fix(store): pointer-based stores, concurrency and robustness fixes#1
fix(store): pointer-based stores, concurrency and robustness fixes#1
Conversation
Agent-Logs-Url: https://github.com/solafide-dev/august/sessions/793d4d09-4d4e-4e9d-a4db-c43d6e01799f Co-authored-by: applehat <262524+applehat@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses multiple correctness and concurrency issues in the August storage library, primarily ensuring store mutations persist (pointer semantics) and removing data races / deadlocks around file watching and registry population.
Changes:
- Switches
August.storagefrom value to pointer semantics (map[string]*AugustStore) and updatesGetStore/Registeraccordingly. - Adds synchronization around
systemModCacheto eliminate concurrent access races. - Hardens startup loading/configuration: avoids deadlocks in
populateRegistry, usesstrings.HasSuffixfor extension checks, and replaces reflection-based config setting with explicit type-checked options.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
go.mod |
Promotes fsnotify to a direct dependency and cleans up requires. |
august.go |
Pointer-based store map, safer GetStore, concurrency fixes for mod cache, safer populateRegistry, config setter refactor. |
augustStore.go |
Fixes inefficient locking in GetAll and synchronizes systemModCache writes in event(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // read the file | ||
| id := fname[:len(fname)-len(ext)] | ||
| log.Printf("Loading file: %s for registry %s as ID %s", fname, name, id) | ||
| store.loadFromFile(id) |
There was a problem hiding this comment.
populateRegistry calls store.loadFromFile(id) but ignores the returned error, which can silently skip corrupted/unreadable entries and leave the in-memory store partially populated. Please handle/propagate the error (or at least log it) so startup failures are visible to callers.
| store.loadFromFile(id) | |
| if err := store.loadFromFile(id); err != nil { | |
| log.Printf("Error loading file: %s for registry %s as ID %s: %v", fname, name, id, err) | |
| return err | |
| } |
| a.mu.RUnlock() | ||
|
|
||
| if !ok { | ||
| return fmt.Errorf("store %s does not exists", name) |
There was a problem hiding this comment.
Error message grammar: "does not exists" should be "does not exist".
| return fmt.Errorf("store %s does not exists", name) | |
| return fmt.Errorf("store %s does not exist", name) |
| // Set a config option. | ||
| func (a *August) Config(k AugustConfigOption, v interface{}) { | ||
| func (a *August) Config(k AugustConfigOption, v interface{}) error { | ||
| a.mu.Lock() | ||
| defer a.mu.Unlock() |
There was a problem hiding this comment.
Changing Config to return an error is a breaking API change for downstream users (previously it was void). If you need to keep compatibility, consider adding a new method (e.g., ConfigE/SetConfig) that returns an error while keeping the old signature as a wrapper (possibly logging or panicking on invalid types), or bumping the module major version if breaking changes are intended.
The library had several correctness and concurrency bugs: mutations to stores were silently lost due to value-copy semantics,
systemModCachewas unprotected against concurrent access, andConfigcould panic on type mismatch via unchecked reflection.Storage: value → pointer semantics
August.storagewasmap[string]AugustStore.GetStorereturned&localCopy, so pointer-receiver mutations (e.g.Set,Delete) operated on a throwaway copy.Concurrency:
systemModCachedata raceevent()appended toparent.systemModCacheandhandleModCacheSkipiterated/deleted from it — both without holding any lock.event()now wraps the append withas.parent.mu.Lock()/Unlock()handleModCacheSkip()now acquiresa.mu.Lock()for the full scan+deleteeventFuncis called outside the lock to avoid re-entrant deadlock if the callback calls back into AugustpopulateRegistry: deadlock + panic fixesThe function held
a.mu.Lock()acrossstore.loadFromFile()→event()→a.mu.Lock()— a self-deadlock. Fixed by reading config/registry data under a briefRLock, releasing before file I/O.File extension check
file.Name()[len-len(format):]could also panic on short filenames; replaced withstrings.HasSuffix.GetStore: wrong lock + copy returnWas using a write lock for a pure read; switched to
RLockand eliminated the redundantelsebranch.Config: reflection-based set → explicit switchreflect...Set(reflect.ValueOf(v))panics if the type doesn't match exactly. Replaced with aswitchonAugustConfigOptionwith type assertions and a propererrorreturn.GetAll: deferred unlock inside loopEach iteration deferred an
RUnlock, piling up N defers. Lock is now acquired once before the loop.Original prompt
This PR fixes multiple correctness, concurrency, and robustness bugs in the solafide-dev/august repository so the library behaves as intended.
Summary of issues found and changes requested:
Files to change: august.go, augustStore.go
Files to change: august.go, augustStore.go
File: august.go
File: august.go
File: augustStore.go
File: august.go
Tests: There are no unit tests in the repo. This PR focuses on correctness fixes. I will:
go vetandgo buildlocally to ensure code compiles.Please open a pull request in solafide-dev/august with these changes targeting the default branch. If you want me to target a specific branch or include additional changes (e.g., add unit tests, CI), tell me and I'll update the PR.
Changes to implement (detailed):
august.go:
augustStore.go:
If this plan looks good I will implement the changes and open the PR.
The following is the prior conversation context from the user's chat exploration (may be truncated):
User: Update my August Library to be functional
Assistant: GitHub Copilot Chat Assistant here. I can help—first I need a few clarifying details so I do the right work.
Questions:
By “functional” do you mean:
Do you want me to:
Which branch should I target (default branch if you don’t specify)? If you want a PR, do you want it against main/master or a specific branch?
Any constraints or priorities (keep API compatibility, support only Node X / Python X / Go X, add tests, add CI)?
If you want me to proceed, I’ll:
Tell me which option you prefer and confirm whether I should scan the repo now.
User: Scan it, find bugs, fix bugs, open a PR. Make it not suck. Lol
Assistant: GitHub Copilot Chat Assistant here ...
This pull request was created from Copilot chat.