Skip to content

Implement cleanup and one retry on reserve exhaustion - #174

Merged
plx merged 4 commits into
mainfrom
agent/issue-101-cleanup-retry
Jul 27, 2026
Merged

Implement cleanup and one retry on reserve exhaustion#174
plx merged 4 commits into
mainfrom
agent/issue-101-cleanup-retry

Conversation

@plx

@plx plx commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • defer exhausted single-port reservations to execution-time cleanup and one retry
  • recheck same-key state, allocation, enabled prune/expire predicates, and persistence under one IMMEDIATE transaction or caller-owned savepoint
  • preserve the published PlanAction and Error::PortExhausted variant shapes while exposing deferred-plan state and typed exhaustion details through additive methods
  • classify reserve-retry lock contention with the connection’s configured timeout and phase-specific operation context
  • honor effective disable flags independently of --force, and report only aggregate cleanup outcomes
  • cover missing-path pruning, age expiration, every disable mode/config source, rollback, same-key reconciliation, lock contention, refresh serialization, and multiprocess contention
  • update the library and CLI behavior documentation

Stack

Root cause

ReservePlan returned PortExhausted { tried_cleanup: false } immediately after its preferred/fallback allocation scan. The disable options reached the library but no reserve path consumed them, so configured automatic cleanup could never free a stranded port.

Acceptance mapping

  • exhaustion is rechecked after the executor acquires the write transaction
  • only enabled automatic prune/expire phases run, followed by exactly one allocation retry
  • cleanup, retry, and persistence commit atomically; terminal exhaustion rolls cleanup back
  • a same-key row created or changed after planning is reconciled inside that transaction instead of being silently replaced
  • final exhaustion exposes typed cleanup counts/policy and aggregate reserved/excluded/occupied blockers without paths or reservation identities
  • reserve-retry contention returns typed LockTimeout data for transaction start, execution, savepoint, and commit boundaries
  • successful execution returns the actual selected port and aggregate-only cleanup warning
  • public exhaustive matches over the five established PlanAction variants and exact destructuring of Error::PortExhausted { range, tried_cleanup } remain valid

Red evidence

Before implementation:

  • the one-port missing-path case exited 6 with port range 51381-51381 exhausted
  • the one-port expired-existing-path case exited 6 with port range 51389-51389 exhausted
  • combined and environment-derived disable tests lacked the required cleanup-policy diagnostics
  • all four competing reserve subprocesses exhausted the one-port range, yielding zero winners

Review corrections

Exact-head review of ea41eb67c214d1cd3fbe800c57e82ee3089444be identified three P1 issues. Commit b9633c547997e950ef4458c5361694fe229a7869 addresses all three:

  • execution re-reads and reconciles a same-key reservation after acquiring the write transaction
  • deferred cleanup/retry uses the existing create/update plan variants with a private, non-serialized placeholder marker
  • PortExhausted retains its exact two-field public shape and exposes structured cleanup/blocker data through Error::port_exhaustion_details()

Exact-head re-review of b9633c547997e950ef4458c5361694fe229a7869 identified one P2 issue. Commit 0e246d0d7022c96846b2643602cb630823f4b561 addresses it:

  • the shared immediate-transaction/savepoint helper reads the connection’s configured busy timeout and classifies contention at start, execution, savepoint, and commit boundaries
  • a focused public ReservePlan/PlanExecutor regression forces zero-timeout writer contention and asserts the typed timeout plus reserve-specific operation context

Exact-head re-review of 0e246d0d7022c96846b2643602cb630823f4b561 identified one P2 issue. Commit 975307c90bce7bd5ff0b70429252558d7c902a5f addresses it:

  • deferred overwrite plans remember whether the target existed during planning and, if that target disappears before execution, resolve preserved metadata as a new reservation instead of resurrecting values from the deleted row
  • a focused public ReservePlan/PlanExecutor regression covers the disappearance race and verifies omitted project/task metadata is newly inferred

Validation

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
  • cargo check --workspace --all-targets --all-features --locked
  • cargo test --workspace --all-targets --all-features --locked
  • cargo test -p trop-cli --test reserve_command --all-features --locked
  • cargo test -p trop --test planning --all-features --locked
  • cargo test -p trop --test concurrent_operations test_automatic_cleanup_retry_serializes_competing_reserve_processes --all-features --locked
  • cargo test -p trop --all-features operations::reserve --locked
  • cargo build --release --workspace --all-targets --all-features --locked
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked
  • just test-production-readiness-selector
  • agentic-navigation-guide check
  • agentic-navigation-guide verify
  • just preflight-pr

Residual risks

None.

Hosted CI passed on run 30307335347, and independent exact-head re-review of 975307c90bce7bd5ff0b70429252558d7c902a5f reported no major issues.

Closes #101

@plx

plx commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Please independently review exact head ea41eb67c214d1cd3fbe800c57e82ee3089444be against issue #101 and the authoritative reserve/allocation/cleanup contract. Focus on:

  • one serialization boundary covering execution-time exhaustion recheck, enabled cleanup, the single retry, and reservation persistence;
  • whether a competing writer can steal the selected port or a concurrent refresh can be deleted from stale evidence;
  • exactly-once retry behavior after the preferred/fallback scan and cleanup phase ordering/predicates;
  • effective config plus individual/combined disable flags, including --force not re-enabling cleanup;
  • rollback of cleanup when allocation still fails, typed blocker diagnostics, and quiet-mode path privacy;
  • public plan/executor compatibility and macOS/Linux/Windows portability of the regression and multiprocess tests.

Please identify any correctness, race, API, portability, testing, or acceptance-criteria blocker on this exact SHA.

🤖 Posted by Codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ea41eb67c2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread trop/src/operations/reserve.rs Outdated
Comment on lines +650 to +653
if let AllocationResult::Allocated(port) =
allocate_with_fallback(allocation_options, &allocate)?
{
persist_deferred_reservation(conn, key, project, task, created_at, port)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reconcile the reservation key before persisting

When callers use the public ReservePlan/PlanExecutor pattern on an autocommit connection, another writer can create the same key after planning but before this serialized recheck. If that writer also leaves another port free, this branch selects the free port and persist_deferred_reservation unconditionally upserts the key, silently moving the concurrent reservation and replacing its metadata without --overwrite, sticky-field authorization, or an idempotent refresh. Re-read and reconcile the key inside this transaction before allocation/persistence so a matching concurrent reservation keeps its port and metadata.

AGENTS.md reference: AGENTS.md:L13-L17

Useful? React with 👍 / 👎.

Comment thread trop/src/operations/plan.rs Outdated

/// Recheck an exhausted allocation, run the enabled automatic-cleanup
/// phases, retry once, and persist the resulting reservation atomically.
ReserveAfterCleanup {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve compatibility for exhaustive PlanAction matches

PlanAction is a public, crate-root-reexported enum and is not marked non_exhaustive, so adding this variant makes every downstream exhaustive match fail to compile. This is especially problematic because the crate remains version 0.2.0; represent the deferred reserve operation without expanding the existing exhaustive public enum, or make the API break explicit through the project's compatibility/versioning process.

Useful? React with 👍 / 👎.

Comment thread trop/src/error.rs Outdated
Comment on lines +209 to +210
/// Non-destructive explanation of cleanup policy and remaining blockers.
details: String,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Model exhaustion blockers as typed data

The new exhaustion diagnostic stores cleanup policy and all remaining blocker information in a free-form String, so library consumers cannot distinguish disabled phases, reserved ports, exclusions, occupancy, or cleanup counts without parsing display prose. That fails the typed-blocker acceptance contract and makes wording changes an accidental API; expose structured enums/fields for the blocker and cleanup outcome, then derive the human-readable message from them.

Useful? React with 👍 / 👎.

@plx

plx commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Please independently re-review exact head b9633c547997e950ef4458c5361694fe229a7869. The prior exact-head review identified same-key reconciliation, public PlanAction compatibility, and typed exhaustion diagnostics as P1 findings; commit b9633c5 addresses all three. Please report only findings that remain on this exact head.

🤖 Posted by Codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b9633c5479

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread trop/src/database/operations.rs Outdated
return Self::with_savepoint(conn, savepoint_name, operation);
}

let transaction = Transaction::new_unchecked(conn, TransactionBehavior::Immediate)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Classify contention at the deferred transaction boundary

When a library caller executes an exhausted ReservePlan through PlanExecutor on an autocommit Database::connection() while another writer holds the database, this newly introduced BEGIN IMMEDIATE can return SQLITE_BUSY/SQLITE_LOCKED after the configured timeout. The direct ? converts that into Error::Database, unlike Database::begin_transaction and other public database boundaries, so callers lose the documented typed Error::LockTimeout and its timeout/operation context; classify lock errors when starting (and committing) this transaction.

Useful? React with 👍 / 👎.

@plx

plx commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Please independently re-review exact head 0e246d0d7022c96846b2643602cb630823f4b561. The prior exact-head review found that the deferred transaction boundary did not classify SQLite contention; this head reads the connection busy timeout, classifies start/execution/savepoint/commit lock failures, and adds a public-plan zero-timeout regression. Please report only findings that remain on this exact head.

🤖 Posted by Codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0e246d0d70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread trop/src/operations/reserve.rs Outdated
Comment on lines +637 to +638
project: placeholder.project().map(ToOwned::to_owned),
task: placeholder.task().map(ToOwned::to_owned),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Re-resolve metadata when an overwritten key disappears

When an overwrite/force plan is deferred after exhaustion and another connection deletes the target key before the public executor runs, this branch copies metadata that was resolved from the now-deleted reservation. For omitted fields, MetadataIntent::Preserve should instead apply new-reservation inference, but execution resurrects the deleted project/task values while treating the row as new by resetting created_at. Resolve these fields from the stored intents with resolve_new when the planned update no longer has an existing row.

AGENTS.md reference: AGENTS.md:L13-L17

Useful? React with 👍 / 👎.

@plx

plx commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Please independently re-review exact head 975307c90bce7bd5ff0b70429252558d7c902a5f. The prior exact-head review found that a deferred overwrite could resurrect preserved metadata if the target disappeared after planning; this head records whether the target existed at planning, resolves omitted metadata with new-reservation inference when it vanishes, and adds a focused public-plan regression. Please report only findings that remain on this exact head.

🤖 Posted by Codex

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 975307c90b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@plx
plx marked this pull request as ready for review July 27, 2026 21:57
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@plx
plx merged commit 73371b3 into main Jul 27, 2026
15 checks passed
@plx
plx deleted the agent/issue-101-cleanup-retry branch July 27, 2026 22:01
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.

Implement configured prune/expire cleanup and one retry on reserve exhaustion

1 participant