fix(form): select document family for update to avoid race conditions - #2645
Merged
winged merged 3 commits intoAug 26, 2026
Merged
Conversation
winged
force-pushed
the
enforce_document_update_locking
branch
2 times, most recently
from
August 25, 2026 14:15
617475e to
bcbb326
Compare
winged
force-pushed
the
enforce_document_update_locking
branch
2 times, most recently
from
August 25, 2026 15:07
ba83f9a to
e5b1371
Compare
Concurrent updates to different answers in the same document family can recalculate the same dependent answers. Without a common lock, either transaction may calculate results without seeing the other transaction's uncommitted changes and later overwrite those results. So we lock the family's root document row before reading the existing answer, validating, updating, and recalculating. Writers using these API entry points now acquire the same row lock, so PostgreSQL serializes their work within a document family. The lock is held until the surrounding transaction commits. To ensure all GraphQL answer-save mutations use the locking path, refactor the answer serializers to use the common API as well.
SaveAnswerLogic.get_new_answer() now already extracts file input and updates the file relation after creating or updating the answer. Remove the duplicate handling from create() and update() so file relations are processed exactly once through the common save path.
winged
force-pushed
the
enforce_document_update_locking
branch
from
August 25, 2026 15:32
e5b1371 to
c683532
Compare
nlzet
requested changes
Aug 26, 2026
nlzet
left a comment
Contributor
There was a problem hiding this comment.
To make it complete there are currently 3 GraphQL paths that bypass locking:
DocumentSerializer::update(inherited parentupdateso will not lock)RemoveAnswerSerializer::updateRemoveDocumentSerializer::update
Ensure document updates and answer/document removals acquire the family-level `SELECT FOR UPDATE` lock. Route these mutations through the API layer and add regression tests for locking, single-pass validation, and metadata preservation.
winged
force-pushed
the
enforce_document_update_locking
branch
from
August 26, 2026 11:57
cf33f0b to
86b593b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Concurrent updates to different answers in the same document family can
recalculate the same dependent answers. Without a common lock, either
transaction may calculate results without seeing the other transaction's
uncommitted changes and later overwrite those results.
So we lock the family's root document row before reading the existing answer,
validating, updating, and recalculating. Writers using these API entry points
now acquire the same row lock, so PostgreSQL serializes their work within a
document family. The lock is held until the surrounding transaction commits.
To ensure all GraphQL answer-save mutations use the locking path,
refactor the answer serializers to use the common API as well.