Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

moderation: added lock mechanism. #101

Merged

Conversation

alejandromumo
Copy link
Member

@alejandromumo alejandromumo commented Aug 11, 2023

closes #102

NEEDS:

Tests are failing since it needs the PR linked above.

Locally tests are passing:

Name                                                         Stmts   Miss  Cover   Missing
------------------------------------------------------------------------------------------
invenio_users_resources/__init__.py                              3      0   100%
invenio_users_resources/administration/__init__.py               1      1     0%   9
invenio_users_resources/administration/views/__init__.py         1      1     0%   9
invenio_users_resources/administration/views/users.py           35     35     0%   9-69
invenio_users_resources/config.py                               12      0   100%
invenio_users_resources/entity_resolvers.py                     74     36    51%   35-42, 46-50, 54, 58-59, 63-79, 93, 97, 101, 105, 114-120, 124-126, 130-131, 146, 150, 154, 158
invenio_users_resources/ext.py                                  49      4    92%   104-107
invenio_users_resources/forms.py                                15     15     0%   9-41
invenio_users_resources/models.py                               11     11     0%   9-39
invenio_users_resources/notifications/__init__.py                1      0   100%
invenio_users_resources/notifications/filters.py                 9      0   100%
invenio_users_resources/notifications/generators.py             11      0   100%
invenio_users_resources/permissions.py                           4      0   100%
invenio_users_resources/proxies.py                               8      0   100%
invenio_users_resources/records/__init__.py                      3      0   100%
invenio_users_resources/records/api.py                         133     23    83%   112, 117, 125-129, 144-149, 153, 235-242, 248-251, 270, 280
invenio_users_resources/records/dumpers/__init__.py              3      0   100%
invenio_users_resources/records/dumpers/email.py                16      0   100%
invenio_users_resources/records/hooks.py                        31      5    84%   36, 41-44
invenio_users_resources/records/mappings/__init__.py             1      0   100%
invenio_users_resources/records/mappings/os-v1/__init__.py       1      1     0%   9
invenio_users_resources/records/mappings/os-v2/__init__.py       1      1     0%   9
invenio_users_resources/records/mappings/v7/__init__.py          1      0   100%
invenio_users_resources/records/models.py                       66      6    91%   37, 67, 73, 78, 87, 96
invenio_users_resources/resources/__init__.py                    4      0   100%
invenio_users_resources/resources/groups/__init__.py             4      0   100%
invenio_users_resources/resources/groups/config.py              11      0   100%
invenio_users_resources/resources/groups/resource.py            25      4    84%   44-49, 55-59
invenio_users_resources/resources/users/__init__.py              4      0   100%
invenio_users_resources/resources/users/config.py                9      0   100%
invenio_users_resources/resources/users/resource.py             27      3    89%   32, 48-53
invenio_users_resources/services/__init__.py                     4      0   100%
invenio_users_resources/services/common.py                       6      0   100%
invenio_users_resources/services/decorators.py                  33      4    88%   35, 41-43
invenio_users_resources/services/generators.py                  57      8    86%   32, 69-74, 101, 110
invenio_users_resources/services/groups/__init__.py              4      0   100%
invenio_users_resources/services/groups/config.py               31      0   100%
invenio_users_resources/services/groups/results.py              58     23    60%   28-34, 39, 43, 48, 53, 58-72, 77, 81-84, 126
invenio_users_resources/services/groups/service.py              27     12    56%   29-41, 48, 54-58
invenio_users_resources/services/groups/tasks.py                22      4    82%   27-28, 38-39
invenio_users_resources/services/params.py                      10      0   100%
invenio_users_resources/services/permissions.py                 23      0   100%
invenio_users_resources/services/results.py                     26      0   100%
invenio_users_resources/services/schemas.py                     47      0   100%
invenio_users_resources/services/users/__init__.py               4      0   100%
invenio_users_resources/services/users/config.py                29      0   100%
invenio_users_resources/services/users/results.py               58      5    91%   39, 43, 77, 83, 126
invenio_users_resources/services/users/service.py              107     14    87%   50, 55-79, 118, 124-125, 134, 140-142
invenio_users_resources/services/users/tasks.py                 42      4    90%   52-53, 63-64
invenio_users_resources/views.py                                19      0   100%
------------------------------------------------------------------------------------------
TOTAL                                                         1181    220    81%

====================================================================================================================================== 62 passed, 15 skipped, 6008 warnings in 26.64s ======================================================================================================================================

@alejandromumo alejandromumo force-pushed the add_moderation_locking_mechanism branch from 7f02362 to 105a804 Compare August 14, 2023 10:47
@alejandromumo alejandromumo marked this pull request as ready for review August 14, 2023 10:48
def execute_moderation_actions(user_id, action):
@shared_task(ignore_result=True, acks_late=True, retry=True)
@lock_if_renew(
lock_prefix="user_moderation_lock",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment about this in the invenio-cache PR

Copy link
Contributor

@ntarocco ntarocco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After further discussions also with @zzacharo @jrcastro2, we propose to make everything a bit less sophisticated. The drawbacks I see here (and the PR in invenio-cache) are:

  1. the decorator becomes quite complex because of the params parsing
  2. the lock_if_renew feels very tight to the usage in this specific context, not very easy to re-use
  3. the lock is acquired even if the service will trigger a permission denied (and released). We might want to call the lock acquire after the permission check, so that we also have the user param:
def ...
        ...
        user = UserAggregate.get_record(id_)
        if user is None:
            raise PermissionDeniedError()
       
        Mutex(id).acquire()  # `acquire_or_fail` or something

If you want to keep the decorator, here the proposal:

  • the 2 decorators should be defined here, and they are used only in their specific context. lock_user_moderation should assert the id_ as second param, and use it. Similar for the celery task decorator. Such decorators should be used only here, cannot be re-used anywhere else. The only responsibility of the decorator is to calculate the cache key:
        ...
        def decorate(*args, **kwargs):
            user_id = args[1]
            cache_key  = f"user_moderation_lock.{user_id}"
            ...

I would not make configurable the prefix, I would remove USERS_RESOURCES_MODERATION_LOCK_KEY_PREFIX.

  • the decorator lock_if_renew is also releasing the lock at the end. It should be defined here, and used only here. Given that it is a decorator of a celery task, is it executed when calling it, or when the task starts? If the latter, it might be better to add it as first statement of the task, instead of a decorator, and take advantage of the mutex context manager:
with Mutex(id).acquire_or_renew():
   ...
   # this will call release at the end

WDYT?

See also comments in the other PR. Let me know what you think, happy to chat IRL.

@alejandromumo
Copy link
Member Author

alejandromumo commented Aug 16, 2023

WDYT?

See also comments in the other PR. Let me know what you think, happy to chat IRL.

I agree that these decorators added a HUGE overhead, it was not worth it.

I ended up dropping them and just having a wrapper of CachedMutex for user moderation. The code looks much cleaner now, thanks!

Example:

def block(identity, id_):
  ...
  
  self.require_permission(identity, "manage", record=user)
  
  # Throws ``LockAcquireFailed`` if not acquired 
  ModerationMutex(id_).acquire()

  ...

@zzacharo zzacharo force-pushed the add_moderation_locking_mechanism branch 3 times, most recently from e40aef0 to 606a07d Compare August 30, 2023 13:10
@zzacharo zzacharo force-pushed the add_moderation_locking_mechanism branch from 606a07d to dddaf7c Compare August 30, 2023 14:24
@zzacharo zzacharo merged commit f645c92 into inveniosoftware:master Aug 30, 2023
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[USER MODERATION] - 4th deliverable - lock moderation actions
3 participants