Repository navigation
fix(TC-1274): bound SQL waits and add an /invoke deadline - #347
Merged
Merged
Conversation
Contributor
Author
|
REQUEST_CHANGES: see TC-1274 (reviewed head 54deefd) |
SQL requests wait at most configurable bounds for the per-database operation lock, for hydration and for durable artifact transfers, then answer 503 sql_busy with the stage and Retry-After: 5. Hydration runs as one joinable task per database that keeps warming after its callers give up. /invoke has a deadline (55 s by default) that stops a request only before its first data write and answers 503 request_timeout; a request that has started writing completes and reports its outcome. A SQL write and its durable save run on a task that owns the operation lock, so a cancelled caller can never leave a write live but not persisted. Typed warnings are mapped in the log shipper (canonical codes reserved in tinycloud-dev#147).
…ixes - Mark a write statement as possibly applied when its future is dropped before the result arrives, so a save that expires after its UPDATE was sent answers outcome_unknown instead of a retryable sql_busy (PostgreSQL still commits it). PostgreSQL regression test included. - Hydrate at most twice per handle() call; retire a hydration's registry entry and report a failure if its task panics. - Log the deadline once; label identity resolution as its own stage; latch publication only for unfreeze_legacy; assert route latches. - Map the hydration_max warning to sql_hydration_timeout. - CHANGELOG: DuckDB waits are not bounded (TC-1412). - Tests: hydration_max expiry, failed-hydration joiners, panicking hydration, write refused past its deadline, no leftover temp files, and a Rocket-level request_timeout with Retry-After.
samgbafa
force-pushed
the
skgbafa/tc-1274-sql-wait-bounds
branch
from
October 18, 2026 00:26
54deefd to
80cb849
Compare
…w lock
The previous regeneration passed '$${TINYCLOUD_LOGSHIP_PROFILE}' as a
String.replaceAll replacement, where '$$' means a single '$', so compose
would have interpolated the profile. Re-pin docker.yml's n0_hash, the N0
compose fixture and the documented lock to the new raw compose.
Contributor
Author
|
REQUEST_CHANGES: see TC-1274 (reviewed head d0616fa) |
…ng the registry lock Remove scripts/ci/__pycache__ and ignore __pycache__/. Prepare the hydration task under the registry lock but spawn it after releasing the lock, so a task dropped at once during runtime shutdown can retire its entry without waiting on a lock its spawner holds.
Contributor
Author
|
APPROVE: see TC-1274 (reviewed head 4ef15a2) |
This was referenced Oct 18, 2026
Above every bounded wait on the path, so a slow stall surfaces as a typed Busy(stage) and only an unbounded wait trips the timeout.
…ait-bounds # Conflicts: # .github/workflows/docker.yml # CHANGELOG.md # docs/releasing.md # docs/runbooks/tc-959-n0-log-shipping.md
Contributor
Author
|
APPROVE: see TC-1317 (delta review of head |
Merged
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.
Refs TC-1274. Merge only after release 1.21.0. It also depends on tinycloud-dev#147 (canonical event codes);
log-shipper/canonical.jsonpins that PR's head, and is re-pinned once it lands.This bounds and reports SQL stalls. It does not fix their cause, which is still unknown. On 2026-10-17, one
/invokewas held for 1,001.8 s and then answered 503, while the ingress had already answered 504 at 60 s. Nothing bounded the per-database operation lock, the hydration lock, the artifact transfer, or the request itself.Changes
Bounded SQL waits. All are configurable under
storage.sql.lock_wait_timeout_ms(30 s): the per-(space, db) operation lock.hydration_wait_timeout_ms(30 s): a caller waiting for the database to be loaded into the cache.artifact_transfer_timeout_ms(45 s): the export probe load, the full-space size check,saveandsave_delta.503 sql_busy: <lock_wait|hydration|artifact_transfer>: …withRetry-After: 5. A save that expires after its BEGIN still answers500 outcome_unknown.Hydration as one joinable task per database.
hydration_max_ms(10 min), so the next request finds the database warm.tokio::fsrenames kept running./invokedeadline.invocation.deadline_ms: 55 s by default, below the ingress's 60 s;0turns it off.tinycloud_core::request_cancelholds a per-request deadline and a one-way commit latch. The latch and the cut are a single compare-and-swap..await, and the response is503 request_timeout: no change was applied; retry the requestwithRetry-After: 1.Request completed after its deadline.Cancellation safety.
Logging. New typed WARN events, with no identifiers beyond the existing request pseudonyms:
SQL busy: lock wait|hydration|artifact transfer(error_class,stage,bound_ms,waited_ms);Request deadline exceeded(stage,deadline_ms);Request completed after its deadline.The log shipper maps them to
sql_busy_lock_wait,sql_busy_hydration,sql_busy_artifact_transfer,request_timeoutanddeadline_overrun. Ownership of these codes was agreed with the TC-959 work. The vendoredfields.jsonand the embedded compose Vector config are regenerated.Follow-ups, not in this PR
database_migration::resolvealso readsdatabase_artifactbefore the SQL service runs. Only the request deadline bounds it today.Tests
New tests:
sql::servicetc1274_*:sql_busy: lock_wait, and another database is unaffected;sql_busy: artifact_transferto two callers, with a single load, and the database is still warm afterwards;request_cancelunit tests, including a late latch from another task after the cut.sql_busyand 1 forrequest_timeout.RED first. Before the implementation, with only the API surface added, 4 of the 5
tc1274_*service tests failed at their assertions.…_cut_by_its_deadline_releases_the_lockalready passed on the old code, because dropping the future released the guard. It stays as a guard for the new spawned-task design.Runs, local, native PostgreSQL 18, no Docker
cargo test -p tinycloud-core: 478 lib tests plus integration tests pass.cargo test -p tinycloud-core --features duckdb: 524 pass. That was 4 of 5 full runs: one run under host load average ~20 hadtransient_storage_errors_are_unavailable_and_release_the_lockexceed its 10 s per-request timeout, and it did not reproduce in 15 isolated runs or 4 more full runs.cargo test -p tinycloud-node(withTINYCLOUD_TEST_BLACKHOLE=1): all pass.cargo clippy --workspace --all-targets -D warnings, plus clippy with--features duckdb, andcargo fmt --check: clean.node scripts/tc959-check.mjs --canonical …passes, thenode --testsuites give 32 pass, and native Vector 0.59.0vector testpasses for both profiles, including the 5 new cases.tests/node-sdkagainst this branch: 53 pass, 36 fail. That is the same set asmainbuilt and run the same way; the failures are environment-dependent (hooks SSE, webhooks, encryption).Smoke (a real node on PostgreSQL, through the SDK):
LOCK TABLE database_artifact IN EXCLUSIVE MODEblocks artifact writes. A write held in its save answered503 sql_busy: artifact_transferafter 6.1 s (bound 6 s), and nothing was applied: the count was unchanged after the lock was released. A concurrent read answered503 sql_busy: lock_waitafter 2.05 s (bound 2 s).ACCESS EXCLUSIVEblocks all artifact access. With a 5 s deadline, a read answered503 request_timeoutafter 5.03 s, and the next read after release succeeded.Review round 1 (TC-1274 comment 0487d405): changes at
80cb849andd0616faThe branch is rebased onto main
65d0e3c(after #346), and the composeVECTOR_CONFIG_JSONis regenerated.canonical.jsonis re-pinned to tinycloud-dev#147 head5a8280c.RecoveringConnectionmarks a write as possibly applied when a write statement's future is dropped before its result arrives. This uses a drop guard,InFlightWrite, inrun_statement,execute_unpreparedandexecute_idempotent. Asave/save_deltathat expires after sending its statement now answers500 outcome_unknown, neversql_busy.postgres_a_save_expiring_after_its_write_was_sent_is_an_unknown_outcomeon PostgreSQL. The delta UPDATE is held byLOCK TABLE database_artifact IN EXCLUSIVE MODE. Its bound expires, then the lock is released, and the in-flight UPDATE commits exactly once.Busy(ArtifactTransfer). GREEN with it.handle()hydrates at most twice, then answersInternal("database actor did not start after hydration").RetireFlightdrop guard retires the registry entry, only if it is still this flight, and publishesFailedif the hydration task panics. Test:tc1274_a_panicking_hydration_does_not_wedge_the_database.hydration_maxwarning maps tosql_hydration_timeout, the 6th code in TC-268: add production invoke performance baseline #147. Callers that get that outcome now logSQL busy: artifact transfer.Request deadline exceededis logged exactly once, by whoever moves the request from open to cut.unfreeze_legacylatches publication at the route, and the comment is corrected.debug_assert!.identity_resolution. The SQL path still resolves twice per request, and TC-1403 bounds it.saves_waitinginstead ofsleep(500ms).hydration_maxexpiry and registry cleanup;begin_writerefusing a write that is past its deadline;503 request_timeoutwithRetry-After: 1and CORS.sql_busy: hydration(writing-files) stage. That phase is localtokio::fswork and can't be stalled deterministically without a test-only hook.NETWORK_ERROR,Retry-Afteris ignored, and a 503 on a conditional KV put is reported asKV_CONFLICT.Local runs at
80cb849(native PostgreSQL 18, no Docker):cargo fmt --check: clean.-Dwarnings, and with--features duckdb): clean.cargo test -p tinycloud-core --features duckdb: no failures.cargo test -p tinycloud-node --no-fail-fastwithTINYCLOUD_TEST_BLACKHOLE=1: no failures.tc959-check --canonicalpasses;node --testgives 33 pass;d0616fa: compose fix. The first regeneration passed'$${TINYCLOUD_LOGSHIP_PROFILE}'as aString.replaceAllreplacement string, and in a replacement string$$means a single$. The embedded config therefore lost its compose escape, andhosted-compose-proofcaught it. The compose is now regenerated with a function replacement. The N0 raw lock is re-pinned to6e3b96c8…indocker.ymln0_hash,scripts/ci/fixtures/tc959-n0-compose.yaml,docs/releasing.mdand the TC-959 runbook.scripts/ci/test-deploy-recovery.pypasses locally.CI at
d0616fa: all green, includingtest (workspace, PostgreSQL-backed),hosted-compose-proof,Version guard, clippy and fmt.