Services running propagations
file | service | groups ancestors | items ancestors | permissions | results |
---|---|---|---|---|---|
app/api/answers/submit.go | itemGetAnswerToken | sync (if at least one item is unlocked by the results propagation) | 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 or at least one item is unlocked by the results propagation) | 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) | sync (if groups_groups.expires_at is changed and at least one item is unlocked by the results propagation) | sync (if groups_groups.expires_at is changed) | |
app/api/currentuser/accept_group_invitation.go | groupInvitationAccept | sync (if the group is not a team) | sync (if at least one item is unlocked by the results propagation) | 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) | sync (if at least one item is unlocked by the results propagation) | 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) | sync (if at least one item is unlocked by the results propagation) | 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 or at least one item is unlocked by the results propagation) | 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) | sync (if at least one item is unlocked by the results propagation) | 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 | sync (if at least one item is unlocked by the results propagation) | 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) | sync (if at least one item is unlocked by the results propagation) | 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 | sync (if ‘can_view’ or ‘is_owner’ is changed) | ||
app/api/items/apply_dependency.go | itemDependencyApply | sync (if at least one item is unlocked) | sync (if at least one item is unlocked) | ||
app/api/items/ask_hint.go | itemGetHintToken | sync (if at least one item is unlocked by the results propagation) | sync | ||
app/api/items/create_attempt.go | attemptCreate | sync (if at least one item is unlocked by the results propagation) | 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 | sync | |
app/api/items/end_attempt.go | itemAttemptEnd | sync | |||
app/api/items/enter.go | itemEnter | sync (if items.participants_group_id is not null) | sync (if items.participants_group_id is not null and at least one item is unlocked by the results propagation) | sync (if items.participants_group_id is not null) | |
app/api/items/generate_task_token.go | itemTaskTokenGenerate | sync (if at least one item is unlocked by the results propagation) | sync | ||
app/api/items/save_grade.go | saveGrade | sync (if at least one item is unlocked by the results propagation) | sync | ||
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:
- It tries to schedule an async propagation of the specified kind by calling a special HTTP endpoint.
- If the endpoint succeeds, the specified propagation really runs async.
- 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 three propagations:
- permissions,
- results.
🤷
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, 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, it’s possible that a sync results propagation is waiting for an async results propagation to finish completely. This can lead to lock wait timeouts resulting in a server error response. 🤷
Happily, we have a solution for this: we can completely disable sync results propagations (with a special config setting) and run only async results propagations. This is the only way to avoid lock wait timeouts.