Skip to content

feat(load): retry a load while another load holds the table - #335

Merged
eddietejeda merged 1 commit into
mainfrom
feat/load-retry-resource-locked
Oct 15, 2026
Merged

eddietejeda merged 1 commit into
mainfrom
feat/load-retry-resource-locked

Conversation

@eddietejeda

Copy link
Copy Markdown
Contributor

Why

Loads into one table are serialised server-side. A second writer gets a 409 with code RESOURCE_LOCKED and a Retry-After header. The CLI printed the error and exited 1, so concurrent appends from several processes mostly failed. In Tenki's integration run, 7 of 8 concurrent databases load --append calls exited 1.

What

  • databases load and databases tables load retry a RESOURCE_LOCKED 409, honouring Retry-After (fallback 5s), for up to --retry-timeout seconds. Default 60; 0 sends exactly once.
  • Matched on the error code, never the message text, so a permanent CONFLICT (idempotency-key reuse, catalog scoped elsewhere) is still fatal.
  • The spinner reports each pause so a long wait is never silent. On timeout the server's own 409 message is printed as before.
  • Api::post_raw_with_headers added so the load path can read Retry-After; post_raw delegates to it.
  • Skill docs updated.

Tests

  • lock_retry_after_matches_resource_locked_only: code match, header parse, fallback, and the non-retry cases.
  • load_retries_resource_locked_until_it_succeeds: two 409s then a 200 against mockito.
  • load_retry_timeout_zero_sends_once_and_returns_the_409.

Loads into one table are serialised server-side. A second writer gets a
409 whose code is RESOURCE_LOCKED and whose Retry-After says how long to
hold off. The CLI printed the error and exited 1, so concurrent appends
from several processes mostly failed (7 of 8 in Tenki's run).

`databases load` and `databases tables load` now retry that 409, honouring
Retry-After, for up to --retry-timeout seconds (default 60; 0 sends once).
Matched on the error code, never the message, so a permanent CONFLICT is
still fatal. The spinner says when it is waiting and for how long.
@eddietejeda
eddietejeda requested a review from a team as a code owner October 15, 2026 19:07
@eddietejeda
eddietejeda requested review from anoop-narang and removed request for a team October 15, 2026 19:07
Comment thread src/commands/databases.rs
spinner.set_message(format!(
"Loading table... another load holds the table, retrying in {}s",
wait.as_secs()
));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The spinner keeps showing "retrying in Ns" after the sleep ends and during the next attempt (not blocking).

Fix: reset the spinner message to "Loading table..." after the sleep. One way is to pass a second callback, or to set the message before each post_raw_with_headers call. Without the reset, the user sees a stale countdown while the retry request runs.

Comment thread src/commands/databases.rs
.and_then(|v| v.to_str().ok())
.and_then(|v| v.trim().parse::<u64>().ok())
.map(std::time::Duration::from_secs)
.unwrap_or(LOCK_RETRY_FALLBACK);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: A Retry-After: 0 makes post_load_with_retry send requests in a tight loop until the deadline (not blocking).

Fix: set a minimum wait, for example .max(Duration::from_secs(1)), on the parsed value. RFC 9110 allows Retry-After: 0. With a 60s budget and no minimum, one CLI process can send hundreds of POSTs to a locked table. The load_retries_resource_locked_until_it_succeeds test uses "0", so the test needs a different header value or a test-only minimum.

@eddietejeda
eddietejeda merged commit fc2274e into main Oct 15, 2026
14 checks passed
@eddietejeda
eddietejeda deleted the feat/load-retry-resource-locked branch October 15, 2026 19:16
@codecov

codecov Bot commented Oct 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.05691% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/commands/databases.rs 91.74% 9 Missing ⚠️
src/main.rs 50.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant