Locks are written out at each call site, with four key conventions and no written order #186
Open
opened 2026-10-06 21:25:45 +00:00 by cgalo5758
·
1 comment
Labels
Clear labels
accessibility
area/billing
area/catalog
area/discourse
area/domains
area/entitlements
area/fedwiki
area/identity
area/integrations
area/licensing
area/member-ui
area/meta
area/operator-ui
area/ops
area/testing
duplicate
good-first-issue
invalid
privacy
security
upstream
wontfix
A barrier for people using assistive technology or a keyboard alone.
The Stripe mirror, checkout, subscriptions, invoices, fulfillment.
Products, prices, plan ladders, purchasability.
The Discourse integration.
The domains registry, claims, placements, the certificate ask.
Entitlement sets, rules, grants, pools, provisioning.
The Federated Wiki integration and farm sync.
Sign-in, sessions, persons, organizations, workspaces, roles.
The provider registry, outbox and webhooks in general.
Licenses, the contributor agreement, SPDX headers.
Member pages.
The repository itself, its contributing guide, CI, the tracker and the workflow.
Operator pages, forms, lists, the design system.
Deployment, configuration, migrations, workflows, instance settings.
The test stack, screens, lint, walkthroughs.
Closed because another issue already covers it.
Small, self-contained, and explained enough to be a first contribution.
Closed because it is not a ticket for this repository.
Touches what a person's data reveals.
Touches authentication, authorization, secrets or data exposure.
Waits on another repository or project before it can move.
Closed because it will not be done, with the reason in the last comment.
kind
bug
The software does something other than what it promises; closed when it again does what it promises.
kind
debt
Code, tests or tooling to clean up with nothing visible changing; closed when they are cleaner.
kind
design
A question to settle before work can be defined; closed when the decision is written down.
kind
docs
Documentation that is wrong or missing; closed when it says the right thing.
kind
enhancement
Something the software does not do yet; closed when it does.
priority
critical
Blocks the active milestone or harms members now.
priority
high
Next in line inside the active milestone.
priority
low
Inside the active milestone, when nothing else is left.
priority
medium
Inside the active milestone, after the high ones.
Milestone
No items
No Milestone
Public launch
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: wiki-cafe/member-console#186
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The pool row lock is SQL in five places (
internal/entitlements/grant_acts.gotwice,grant_resumption.go,internal/server/operator_org_types.go,operator_plan_ladders.go) beside two generated queries doing the same. Advisory keys are built four ways:hashtextof a bare id (workspace, product, subscription, invoice), sharing one 32-bit space;hashtextextendedof a prefixed string (lifecycle, desired state);hashtextextendedof a bare name (materialization); a constant (domains registry). The entitlements spec fixes part of the order; the rest lives in call-site comments, and the plan-ending paths take the pool lock at different points (#180). #165 adds a usage-row lock under the workspace lock with nothing to check it against.Done when: lock functions live in one place; every advisory key is built from a family prefix and an id; the families and their order are written once under
docs/; the project lint allowspg_advisory_xact_lockand poolFOR UPDATEonly in the helpers; and the deploy that changes keys runs no old process beside new ones.Part of this landed with the test-kit step of #166:
db.InTxanddb.Beginininternal/dbopen a transaction with a kind and return its begin and commit errors unwrapped.entitlements.Materializingandentitlements.RuleChangetake the rendezvous lock and setlock_timeout; a plain transaction does neither. Every transaction that materializes or changes a rule opens this way.LockPoolsandLockPoolOfGrantininternal/entitlementsare the only Go callers of the two queries that lockcore.resource_pools, andLockPoolstakes several pools in ascending order. No other Go code locks a pool row. The database functionscore.confer,core.end_conferral,core.align_conferral_shape,core.update_conferral_boundsandcore.settle_obligationstill lock it themselves, asdocs/database-locks.mdlists.What remains:
One lock order is inverted (#196): recording a failed drain locks the obligation row before the pool row, while the drain locks the pool first, so the two can deadlock.
ReorderPlanLaddershas a smaller case of the same kind: it updates each ladder'ssort_orderin the order the drag submitted, so two opposite reorders at the same moment can deadlock.The advisory key families are each taken by their own callers:
hashtextextended('entitlement_materialization'), always first (test/mockshot-seed.sqlalso takes it, which the doc does not mention);hashtextextended:lifecycle:<instance>before the site and usage rows, anddesired-state:<connection>:<kind>:<recipient>before the record row;hashtext: the Stripe subscription (after the rendezvous, before the pool rows), and the Stripe invoice, the product and the FedWiki workspace, each taken first;The order written once.
docs/database-locks.mdstates it, but each caller takes its own locks, so nothing holds a new path to it.The lint rule. Nothing refuses
BeginTxoutsideinternal/db; the only guard is a comment ininternal/entitlements/tx.goasking that the call stay greppable. A forbidigo pattern like the two in.golangci.ymlcan hold it once the sites below convert. Tests open 73 more transactions by hand across 33 files, which the rule would also meet unless it excludes tests.Transactions still opened by hand (14, outside tests). None of them materializes. Converting each to
db.InTxwith no kind changes only how its begin and commit errors read and that rollback runs through the runner, except where noted:internal/domains/registry.go,WithLock: the registry's advisory lock moves into the function or a kind of its own.internal/integration/lifecycle.go,RecordLifecycleRequestandRecordFirstLifecycleRequest: a failed commit today returns the answer along with the error.internal/integration/registration.go,RegisterProviders: its provider stamp oncore.resource_keyswaits behind materializers' foreign-key locks with no timeout; under a rendezvous kind the wait would time out instead.internal/integrations/discourse/workflows/activities.go,inTx: holds the transaction across Discourse HTTP calls, and converting leaves that as it is.internal/integrations/fedwiki/usage/usage.go,BoundWorkspace;internal/integrations/fedwiki/workflows/reconcile.go,sweepLocally;internal/integrations/fedwiki/workflows/request_activities.go,finishAllWith: a return that writes nothing would commit instead of rolling back, with the same effect, and a failed commit today returns a result along with the error.internal/integrations/stripe/workflows/invoice_reconcile.go,converge: its commit error is classified for Temporal's retry; converted, it would read like the function's own already-classified errors.internal/server/operator_billing.go,MakeDefaultPrice: rendering moves after the transaction, and the per-step error labels collapse into one.internal/server/operator_billing.go,SyncProductToStripe: renders its answers while holding the product lock and commits at two points, so converting needs an outcome returned from the transaction, one commit, and rendering after it.internal/server/operator_topology.go,ReorderPlanLadders: rendering moves after the transaction.internal/server/workspace_partials.go,CreateWorkspace: its pool-assignment insert waits behind a pool lock with no timeout; under a rendezvous kind the wait would time out instead.internal/systemtenant/systemtenant.go,Ensure: two commit labels collapse into one.