Deleting the local roles is what takes up the memory: it goes through the whole site, waking up all objects, which can be a lot if you have a big site.
For one client where we ran into this, we solved it with the following patch.
Note that you may need to tweak the depth option to fit your site.
The numbers mentioned in the docstring are for one specific site.
from Acquisition import aq_base
from Products.CMFCore.utils import _checkPermission
from Products.CMFCore.MembershipTool import MembershipTool
def deleteLocalRoles(self, obj, member_ids, reindex=1, recursive=0,
"""Delete local roles of specified members.
This takes far too much memory. See if we can reduce this.
We have tried convincing Zope to release memory by using
savepoints (transaction.savepoint(optimistic=True)). We tried
using the catalog to search for only folderish items, and do
explicit garbage collection. Nothing helped.
Now we add a depth on which we search. This is a fluid depth.
If the object does not need deletion of local roles and there
are no interesting local roles at all, we decrease the depth,
thus eliminating uninteresting folders where the member likely
has no local roles anywhere.
Yes, this may fail to delete some local roles. But at least it
usually finishes within a few seconds instead of about a minute.
And it does not consume over 1.5 GB of memory. So be happy.
Note: with a depth of 4 you would already crawl about 90 percent
of the site, so that would hardly help.
delete = False
if _checkPermission(ChangeLocalRoles, obj):
has_local_roles = False
for user, roles in obj.get_local_roles():
if user in member_ids:
delete = True
if len(roles) == 0:
# I guess this cannot happen, but let's be safe.
elif len(roles) > 1:
has_local_roles = True
elif roles == 'Owner':
# Only one uninteresting role.
has_local_roles = True
# At least one to-be-deleted role has been found.
elif not has_local_roles:
# Nothing deleted at this level, and no interesting local
# roles for other users. Decrease search depth.
depth -= 1
if depth <= 0:
# Ignore the rest of this content tree, if any.
if recursive and hasattr(aq_base(obj), 'contentValues'):
for subobj in obj.contentValues():
self.deleteLocalRoles(subobj, member_ids, 0, 1, depth=depth)
if reindex and hasattr(aq_base(obj), 'reindexObjectSecurity'):
# reindexObjectSecurity is always recursive
MembershipTool._orig_deleteLocalRoles = MembershipTool.deleteLocalRoles
MembershipTool.deleteLocalRoles = deleteLocalRoles
MembershipTool.deleteLocalRoles = MembershipTool._orig_deleteLocalRoles
thanks, I ended with this and it seems to be working now:
def remove_subscriber(self, email):
"""Remove subscriber. We can not use `api.user.delete` as, by
default, it tries to remove member areas and local roles; the
later consumes a lot of memory and is not necessary for us.
membership_tool = api.portal.get_tool('portal_membership')
[email], delete_memberareas=False, delete_localroles=False)