Services running propagations

file service groups ancestors items ancestors permissions results
app/api/answers/submit.go itemGetAnswerToken     mark-or-sync* (if at least one item is unlocked by the results propagation) mark-or-sync*
app/api/auth/create_access_token.go accessTokenCreate sync (if the code is given)   sync (if new badges are loaded and ‘group_membership’ permissions are removed)
+
mark-or-sync* (if new badges are loaded and at least one item is unlocked by the results propagation)
mark-or-sync* (if new badges are loaded)
app/api/auth/create_temp_user.go tempUserCreate sync      
app/api/contests/set_additional_time.go contestSetAdditionalTime sync (if groups_groups.expires_at is changed)   mark-or-sync* (if groups_groups.expires_at is changed and at least one item is unlocked by the results propagation) mark-or-sync* (if groups_groups.expires_at is changed)
app/api/currentuser/accept_group_invitation.go groupInvitationAccept sync (if the group is not a team)   mark-or-sync* (if at least one item is unlocked by the results propagation) mark-or-sync* (if the group is not a team)
app/api/currentuser/create_group_join_request.go groupJoinRequestCreate sync (if the request is automatically accepted and the group is not a team)   mark-or-sync* (if at least one item is unlocked by the results propagation) mark-or-sync* (if the request is automatically accepted and the group is not a team)
app/api/currentuser/delete.go currentUserDeletion     sync (if at least one permissions_granted with source_group_id=user.group_id is removed)  
app/api/currentuser/join_group_by_code.go groupsJoinByCode sync (if the group is not a team)   mark-or-sync* (if at least one item is unlocked by the results propagation) mark-or-sync* (if the group is not a team)
app/api/currentuser/leave_group.go groupLeave sync (if the group is not a team)   sync (if ‘group_membership’ permissions are removed)  
app/api/currentuser/refresh.go userDataRefresh sync (if new badges are loaded)   sync (if new badges are loaded and ‘group_membership’ permissions are removed)
+
mark-or-sync* (if new badges are loaded and at least one item is unlocked by the results propagation)
mark-or-sync* (if new badges are loaded)
app/api/groups/accept_join_requests.go groupJoinRequestsAccept sync (if there are join requests to accept and the group is not a team)   mark-or-sync* (if at least one item is unlocked by the results propagation) mark-or-sync* (if there are join requests to accept and the group is not a team)
app/api/groups/accept_leave_requests.go groupLeaveRequestsAccept sync (if there are leave requests to accept and the group is not a team)   sync (if there are leave requests to accept and ‘group_membership’ permissions are removed)  
app/api/groups/add_child.go groupAddChild sync   mark-or-sync* (if at least one item is unlocked by the results propagation) mark-or-sync*
app/api/groups/create_group.go groupCreate sync      
app/api/groups/create_invitations.go groupInvitationsCreate sync (if at least one join request is automatically accepted and the group is not a team)   mark-or-sync* (if at least one item is unlocked by the results propagation) mark-or-sync* (if at least one join request is automatically accepted)
app/api/groups/create_user_batch.go createUserBatch sync      
app/api/groups/delete_group.go groupDelete sync (if at least one group relation is deleted)   sync (if at least one permissions_granted linked to a removed group via source_group_id is deleted)  
app/api/groups/remove_child.go groupRemoveChild sync (if at least one group relation is deleted)   sync (if at least one permissions_granted linked to a removed group via source_group_id is deleted)  
app/api/groups/remove_members.go groupMembersRemove sync (if at least one member is removed)   sync (if at least one member is removed and ‘group_membership’ permissions are removed)  
app/api/groups/remove_user_batch.go userBatchRemove     sync (if at least one permissions_granted with source_group_id=user.group_id is removed)  
app/api/groups/update_group.go groupUpdate sync (if group members are removed because of approval rules strengthening and the group is not a team)   sync (if group members are removed because of approval rules strengthening and ‘group_membership’ permissions are removed)  
app/api/groups/update_permissions.go updatePermissions     sync mark-or-sync* (if ‘can_view’ or ‘is_owner’ is changed)
app/api/items/apply_dependency.go itemDependencyApply     sync (if at least one item is unlocked) mark-or-sync* (if at least one item is unlocked)
app/api/items/ask_hint.go itemGetHintToken     mark-or-sync* (if at least one item is unlocked by the results propagation) mark-or-sync*
app/api/items/create_attempt.go attemptCreate     mark-or-sync* (if at least one item is unlocked by the results propagation) mark-or-sync*
app/api/items/create_item.go itemCreate   sync (if at least one items_items row is created) async* async*
app/api/items/delete_item.go itemDelete   sync sync *res_sync
app/api/items/end_attempt.go itemAttemptEnd sync      
app/api/items/enter.go itemEnter sync (if items.participants_group_id is not null)   mark-or-sync* (if items.participants_group_id is not null and at least one item is unlocked by the results propagation) mark-or-sync* (if items.participants_group_id is not null)
app/api/items/generate_task_token.go itemTaskTokenGenerate     mark-or-sync* (if at least one item is unlocked by the results propagation) mark-or-sync*
app/api/items/save_grade.go saveGrade     sync (if at least one item is unlocked by the results propagation, processes only changes made by this request) sync (without the named lock, processes only changes made by this request)
app/api/items/start_result.go resultStart       async* (if the result is inserted or updated)
app/api/items/start_result_path.go resultStartPath       async* (if the result are to be inserted)
app/api/items/update_item.go itemUpdate   sync (if children are modified) async* (if children are modified) async* (if children/no_score/validation_type are modified)

async* works as follows:

  1. It tries to schedule an async propagation of the specified kind by calling a special HTTP endpoint.
  2. If the endpoint succeeds, the specified propagation really runs async.
  3. If the endpoint fails (due to a network error, a 3-second timeout, or a non-200 HTTP response), no matter which of propagations it tried to schedule, it synchronously runs the sequence of two propagations:
    • permissions,
    • results.

🤷

mark-or-sync* means that, in the context where the backend server has server.disableResultsPropagation=1 (as it must be now in prod), it marks objects as to be propagated, but does not launch any sync propagation nor call the async-propagation endpoint. If server.disableResultsPropagation=0, it does the propagation synchronously.

Notes

Objects marked for propagations are shared between all DB sessions

As of April 2024, since, instead of running sync permissions/results propagations in the same transaction, we run them in separate transactions (except for the saveGrade service), objects marked for propagations are shared between all DB sessions. This means that if a sync propagation is run in one DB session, it processes all objects marked for this propagation, no matter which DB session marked them. So, when an endpoint runs a sync permissions/results propagation to recompute some objects, it also recomputes all objects currently marked as modified by other endpoints/users. This can lead to surprisingly long propagation times even for small changes. 🤷

Impossibility of running sync and async results propagations simultaneously

It’s easy to see that there are many endpoints that run sync results propagations. At the same time, there are some endpoints that run async results propagations. As the results propagation is run under a named lock (except for the saveGrade service), it’s possible that a sync results propagation is waiting for an async results propagation to finish. This can lead to named lock wait timeouts resulting in server error responses. 🤷

Happily, we have a “solution” for this: we completely disable sync results propagations (see the mark-or-sync* description above) and run only async results propagations (except for the saveGrade service which always runs it synchronously). This is currently the only way we have to avoid named lock wait timeouts.