diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-10-31 09:32:15 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-10-31 09:32:15 +0000 |
| commit | 42c4c670c1d73fc19dc84405497ca59ce5e1faa7 (patch) | |
| tree | 8c98c27338ddc67af7a575acecc96ea5bf47bd29 /server/cmd | |
| parent | ff40abd84236288cc0a82226460f8815ad31b76d (diff) | |
Stop a second device from emptying the library, and recover what one did
I pointed the emulator at the live server to verify that published breaks
actually skip. It worked. It also pushed the emulator's own four-show
subscription list, which replaced the real one, and at 07:07:13 the server
reclaimed every show that device had never heard of: 26 shows and 290 mirrored
episodes, deleted in a second.
Three fixes, in the order they matter.
**The list can no longer be emptied by one push.** It is still replaced
wholesale -- the phone owns it, and reconciling two editable copies of something
changed this rarely costs more in sync bugs than it saves -- but a push that
keeps fewer than half of ten or more feeds is refused with a 409 and logged.
Losing most of a library in one push is a second device or a bug; it is never
somebody unsubscribing. Below ten feeds there is nothing to protect: a new
phone's first push legitimately replaces nothing with everything.
**Recognition is recovered rather than redone.** A transcript is keyed by the
episode id, which comes from the feed and the guid, so it outlives the record
and matches again when the episode is mirrored afresh. 332 transcript files
survived on disk. Reclaim restores them -- 18 immediately, the rest as
re-mirroring reaches them -- instead of spending twenty hours of CPU recognising
text the server is already holding. It refuses a transcript whose last cue runs
past the end of the audio, which is the same check made of a publisher's.
**The pipeline's Save was never wired**, which the security review caught before
it could panic in anger: the extraction moved the call and not the field. It is
now optional, because every test wants it nil, and assumed-non-nil is what
shipped a server that would have died on its first recognised episode.
Reclaim runs before each show is polled as well as at startup, or a re-mirrored
episode would wait for a restart to find the words already sitting next to it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'server/cmd')
| -rw-r--r-- | server/cmd/listend/main.go | 10 |
1 files changed, 10 insertions, 0 deletions
diff --git a/server/cmd/listend/main.go b/server/cmd/listend/main.go index e1329a9..2298db5 100644 --- a/server/cmd/listend/main.go +++ b/server/cmd/listend/main.go @@ -117,6 +117,10 @@ func main() { requests: make(chan request, 64), wakeups: make(chan struct{}, 1), } + // Wired after the worker exists, because it is the worker's own save. The pipeline runs + // for tens of minutes at a time and persists as each episode finishes, so a restart in + // the middle keeps the ones already done. + worker.words.Save = worker.save server := api.New(state, worker, api.Options{ Token: token, @@ -143,6 +147,7 @@ func main() { defer stop() if *once { + worker.words.Reclaim() worker.words.Rederive() worker.pollAll(ctx) // Recognition too, so a deployment check exercises the expensive path rather than @@ -228,6 +233,7 @@ func (w *worker) Request(feedURL, guid, enclosureURL, title string) { } func (w *worker) run(ctx context.Context, every time.Duration) { + w.words.Reclaim() w.words.Rederive() w.save() w.pollAll(ctx) @@ -373,6 +379,10 @@ func (w *worker) pollOne(ctx context.Context, feedURL string) { if fetched > 0 { log.Printf("%s: mirrored %d new episode(s)", channel.Title, fetched) } + // Before anything else looks at this show: an episode just re-mirrored may already have + // its words on disk from before its record was lost, and recognising it again would cost + // five minutes for text the server is holding. + w.words.Reclaim() w.words.Harvest(ctx, show, kept) w.words.Recurring(show) w.detect(ctx, show, kept) |
