~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

Spaces are listed one at a time for each account

Merged
Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:serve/discover-one-at-a-time

What changes

An account's spaces are listed one at a time (spaces::discover), behind a lock per account on App. A second listing waits for the first one to finish instead of running beside it.

Why

Each listing deletes the account's kept space credentials, then asks each space's authority whether the account is still a member. When two listings run at once, a check that started before a removal can return after it. It then writes back a credential the authority issued before the removal, or marks the repository readable again after the newer check took it away. So a member who was removed can still find the repository until the next listing. This is the race behind private_repositories_indexed_here, the test that failed most often in recent test runs: that test clears the throttle on every look, so it starts many listings at once. In prod, the once-a-minute throttle makes overlaps rare, but a slow listing can still overlap the next one.

A lock per account is the smallest fix. It costs no extra calls to the authority, and only listings for the same account ever wait.

How it was checked

Left to CI. The test is still quarantined until 2026-11-21. The failure report in private_repositories_indexed_here says why when it waits past its time will show whether anything else keeps it failing.

Shortcomings

A page read by the member that starts before the removal could still store a credential issued before it. That credential works until it expires, at most a few minutes. The next listing deletes it.

Changeset: none

🤖 Generated with Claude Code

Approved

This adds a per-account async mutex, App::discover_lock, and takes it at the top of spaces::owned. That serializes the listing that runs space::forget, then the joined checks and read_by/unread_by writes. A check that started before a removal can no longer finish after a newer listing's result.

The diff matches the problem you describe. It follows the existing orgs lock pattern. The std Mutex guarding the map is released before the .await, so it isn't held across an await. Only listings for the same account wait. The throttle check in discover still runs before the lock, so a listing that has to wait doesn't start a second one.

Two minor points, neither blocking:

  • The lock map gains one entry per account and never shrinks. orgs does the same, so this is consistent with it.
  • docs/design/access.md covers spaces. It may be worth one line saying listings are serialized per account, if the page describes discovery.

I did not run the tests. As you say, whether private_repositories_indexed_here stops failing will show in CI.

Merged as 370b5e6