Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions internal/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ type Daemon struct {
// triggers a zombie restart, debouncing transient gaps during handoffs.
// Only accessed from heartbeat loop goroutine - no sync needed.
mayorZombieCount int

// cachedKnownRigs caches the result of reading mayor/rigs.json so that
// the 10+ call-sites per heartbeat tick share a single disk read.
// Populated at the start of each heartbeat; cleared afterward.
// Only accessed from heartbeat loop goroutine - no sync needed.
cachedKnownRigs []string
knownRigsCached bool
}

// sessionDeath records a detected session death for mass death analysis.
Expand Down Expand Up @@ -739,6 +746,11 @@ func (d *Daemon) heartbeat(state *State) {
return
}

// Invalidate the per-tick rigs cache so this heartbeat re-reads from disk.
// The cache avoids redundant reads within a single tick (10+ callers),
// while invalidating here ensures we pick up rigs.json changes between ticks.
d.invalidateKnownRigsCache()

d.metrics.recordHeartbeat(d.ctx)
d.logger.Println("Heartbeat starting (recovery-focused)")

Expand Down Expand Up @@ -1778,7 +1790,27 @@ func (d *Daemon) openBeadsStores() (map[string]beadsdk.Storage, error) {
}

// getKnownRigs returns list of registered rig names.
// Results are cached per heartbeat tick to avoid redundant disk reads
// (10+ callers per tick all read the same mayor/rigs.json).
func (d *Daemon) getKnownRigs() []string {
if d.knownRigsCached {
return d.cachedKnownRigs
}
rigs := d.readKnownRigsFromDisk()
d.cachedKnownRigs = rigs
d.knownRigsCached = true
return rigs
}

// invalidateKnownRigsCache clears the per-tick cache so the next call
// to getKnownRigs re-reads mayor/rigs.json from disk.
func (d *Daemon) invalidateKnownRigsCache() {
d.cachedKnownRigs = nil
d.knownRigsCached = false
}

// readKnownRigsFromDisk reads and parses mayor/rigs.json.
func (d *Daemon) readKnownRigsFromDisk() []string {
rigsPath := filepath.Join(d.config.TownRoot, "mayor", "rigs.json")
data, err := os.ReadFile(rigsPath)
if err != nil {
Expand Down
6 changes: 4 additions & 2 deletions internal/doctor/workspace_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"os"
"path/filepath"

"github.com/steveyegge/gastown/internal/util"
)

// TownConfigExistsCheck verifies mayor/town.json exists.
Expand Down Expand Up @@ -175,7 +177,7 @@ func (c *RigsRegistryExistsCheck) Fix(ctx *CheckContext) error {
return fmt.Errorf("marshaling empty rigs.json: %w", err)
}

return os.WriteFile(rigsPath, data, 0644)
return util.AtomicWriteFile(rigsPath, data, 0644)
}

// RigsRegistryValidCheck verifies mayor/rigs.json is valid and rigs exist.
Expand Down Expand Up @@ -310,7 +312,7 @@ func (c *RigsRegistryValidCheck) Fix(ctx *CheckContext) error {
return fmt.Errorf("marshaling rigs.json: %w", err)
}

return os.WriteFile(rigsPath, newData, 0644)
return util.AtomicWriteFile(rigsPath, newData, 0644)
}

// MayorExistsCheck verifies the mayor/ directory structure.
Expand Down
Loading