Repository navigation
fix(storage): make session, peer cache and config files safe to share between processes - #286
Open
ApexAiOfficial wants to merge 5 commits into
Open
ApexAiOfficial wants to merge 5 commits into
ApexAiOfficial wants to merge 5 commits into
Conversation
Several tg processes routinely share one session directory (parallel agent commands, or two agents driving the same account). gotd's FileStorage and the peer cache truncated files in place, so a concurrent reader could load an empty or torn session or cache and fail. - internal/atomicfile: write to a temp file in the same directory, then rename over the target (fsync for sessions/config; no fsync for the cache). - File session store keeps gotd's on-disk format and path. - Peer cache merges entries other processes saved since it was loaded, so concurrent commands no longer drop each other's access hashes, and treats a corrupt cache as empty instead of failing every command. - writeConfig refuses to overwrite with O_EXCL instead of stat-then-write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Atomic replacement stopped torn reads, but two tg processes that both read, merged and replaced the cache still lost each other's entries: an overlapping-writers test (8 writers x 25 saves) lost 18-64 of 200 entries. internal/filelock takes an advisory exclusive lock on a sidecar <file>.lock (flock on Unix, LockFileEx on Windows, no-op where single-process), released by the OS if the holder dies. The peer cache holds it for the whole read-merge-write cycle; the regression test now keeps every entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gotd re-saves the session on every (re)connect to the primary DC, re-reading the file and writing its own auth key into it. A long-running process (e.g. tg watch) that reconnected after another process ran tg login or tg logout therefore wrote its old key back, undoing the login or resurrecting the logged-out session. The file store now remembers the auth key it loaded and, under a lock on <file>.lock, skips a write of that key when the file was re-keyed or removed by someone else. Same-key writers remain last-writer-wins, which is safe: the copies differ only in volatile state such as the server salt. The guarantees are documented on fileSessionStore, and tests cover re-login, logout, same-key writers and this process re-keying. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- writeConfig created the final file with O_EXCL and then wrote into it, so a crash mid-write left a partial config that a retried tg init refused to replace. atomicfile.Create writes and syncs a temp file and hard-links it into place (atomic, fails if the target exists), falling back to an exclusive synced create on filesystems without hard links. - Durable writes now also fsync the parent directory, so a completed rename survives power loss (skipped on Windows, which cannot sync a directory). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows cannot sync a directory handle; use a build-tagged no-op there instead of a runtime GOOS check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Several tg processes routinely share one session directory: parallel commands, or two agents driving the same account. This makes the files they share safe for that.
Torn reads. gotd's
session.FileStorage, the peer cache and the config truncated files in place, so a concurrent reader could load an empty or torn session or cache and fail. Newinternal/atomicfile: write a temp file in the same directory, fsync, rename over the target, fsync the directory. The cache skips the fsyncs, since it can be rebuilt. The file session store keeps gotd's on-disk format and path.Lost updates. Atomic replacement alone doesn't stop two processes that both read, merge and replace the peer cache from dropping each other's entries: an overlapping-writers test (8 writers × 25 saves) lost 18–64 of 200. New
internal/filelocktakes an advisory exclusive lock on a sidecar<file>.lock(flock on Unix,LockFileExon Windows, no-op where tg is single-process; released by the OS if the holder dies). The cache holds it across the whole read-merge-write cycle, and a corrupt cache is rebuilt instead of failing every command.Stale session keys. gotd re-saves the session on every reconnect to the primary DC, writing its own auth key into the file. A long-running process (e.g.
tg watch) that reconnected after another process rantg loginortg logouttherefore wrote its old key back, undoing the login or resurrecting the logged-out session. The store remembers the key it loaded and, under the lock, skips writing that key over a session someone else re-keyed or removed. Same-key writers stay last-writer-wins, which is safe because their copies differ only in volatile state such as the server salt.Config creation.
writeConfigstat'ed and then wrote, which raced. A crash mid-write also left a partial config that a retriedtg initrefused to replace.atomicfile.Createwrites and syncs a temp file and hard-links it into place: atomic, and it fails if the target exists. It falls back to an exclusive synced create on filesystems without hard links.Tests cover atomic replace and create, permissions, symlinked targets, lock exclusivity, overlapping cache writers, corrupt-cache recovery, and the session cases: re-login elsewhere, logout elsewhere, same-key writers, and this process re-keying.
go test -raceand golangci-lint v2.12.1 (Go 1.25) pass.🤖 Generated with Claude Code