Skip to content

feat(bambubuddy): live fleet status, cached - #10

Merged
tech5-opti merged 2 commits into
mainfrom
tushar
Aug 21, 2026
Merged

tech5-opti merged 2 commits into
mainfrom
tushar

Conversation

@tech5-opti

Copy link
Copy Markdown
Collaborator

Machine status only moved when somebody pressed Sync. That is not just a stale UI: the batch planner and machine scheduler read machines.status and current_layer, so a printer that finished an hour ago still looked busy to the thing deciding what to print next. Right now BambuBuddy reports FINISH 406/406 while Tensor shows RUNNING.

Making the UI poll would have made it worse. GET /machine-fleet/:id/live costs two upstream calls — ListPrinters purely to translate a serial into a printer id, then GetStatus — with no cache anywhere on the path. Three tabs × three machines at 10s = 108 requests/min to a service on a laptop at the end of a home connection.

bambu_cache.go

Follows the mutex + TTL + injectable-clock shape of internal/auth/freshness.go. Two things that cache does not have, because polling makes them matter:

  • Single-flight — 20 tabs on a cold cache produce one upstream call. Hand-rolled in ~25 lines rather than adding golang.org/x/sync (only an indirect require today).
  • A detached fetch context — with many viewers sharing one call, the first arrival navigating away would otherwise cancel the request everyone else is blocked on. Tested explicitly, because it regresses silently to "all waiters fail".

Plus negative caching: an unreachable printer costs a full 10s client timeout, and single-flight only collapses arrivals that overlap — staggered tabs don't.

The periodic refresh does not prune

SyncFleetFromBambuBuddy ends in DeleteFleetMachinesNotIn. Putting it on a timer makes a destructive operation automatic: one transiently partial ListPrinters — a restarting BambuBuddy, a Tailscale blip — and machines are deleted, with only those mid-print protected.

Split into SyncFleetFromBambuBuddy (prune, human-initiated) and RefreshFleetFromBambuBuddy (no prune, the timer). RunOnStart: true here unlike batch planning: a fresh deployment's empty machines table is exactly when it should populate itself.

Verification

  • Cache unit tests under -race: TTL, single-flight (20 goroutines → 1 call), leader-cancellation survival, negative caching, TTL-0 passthrough
  • Full backend suite green including the 64s httpapi integration tests
  • cmd/fleetsync against the real fleet: synced 2 printer(s): [opti1 opti2]

Either TTL set to 0 disables caching, so a before/after measurement is a config flip rather than a code change.

🤖 Generated with Claude Code

tech5-opti and others added 2 commits August 21, 2026 15:47
… load

Machine status only moved when somebody pressed Sync. That is not just a
stale UI: the batch planner and machine scheduler read machines.status
and current_layer, so a printer that finished an hour ago still looked
busy to the thing deciding what to print next. Right now BambuBuddy
reports FINISH at 406/406 while Tensor shows RUNNING.

Making the UI poll would have made that worse. GET /machine-fleet/:id/live
costs TWO upstream calls - ListPrinters purely to translate a serial into
a printer id, then GetStatus - with no cache anywhere on the path. Three
tabs watching three machines at a 10s poll is 108 requests a minute to a
service running on a laptop at the end of a home connection.

bambu_cache.go caches both reads, following the mutex+TTL+injectable-clock
shape of internal/auth/freshness.go so the client stays pure transport and
the policy sits by its consumers. Two things that cache does NOT have,
because polling makes them matter:

  - Single-flight. Twenty tabs arriving on a cold cache produce one
    upstream call, not twenty. Hand-rolled in ~25 lines rather than
    adding golang.org/x/sync, which is only an indirect require today.
  - A detached fetch context. With many viewers sharing one call, the
    first arrival navigating away would otherwise cancel the request
    every other waiter is blocked on. There is a test for exactly this,
    because it regresses silently to "all waiters fail".

Negative caching too: an unreachable printer costs a full 10s client
timeout, and without it every poll pays that again - single-flight only
collapses arrivals that overlap, and staggered tabs do not.

The periodic refresh is what keeps the table honest with nobody watching,
and it deliberately does NOT prune. Reconciliation ends in
DeleteFleetMachinesNotIn, so putting the full sync on a timer would make a
destructive operation automatic: one transiently partial ListPrinters - a
restarting BambuBuddy, a Tailscale blip - and machines are deleted, with
only those mid-print protected. Removing a decommissioned printer stays
something a human asks for; refreshing status is the thing that must
happen every minute. RunOnStart is true here, unlike batch planning: a
fresh deployment's empty machines table is exactly when it should populate.

Either TTL set to 0 disables that cache, which makes a before/after
measurement a config flip rather than a code change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@tech5-opti
tech5-opti merged commit 2ad089b into main Aug 21, 2026

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e2dd30bb-f46f-49bb-bee4-a02662acbacf


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

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