-
Notifications
You must be signed in to change notification settings - Fork 0
fix(store): pointer-based stores, concurrency and robustness fixes #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,7 +23,7 @@ type August struct { | |||||||||||
| mu sync.RWMutex | ||||||||||||
| storeRegistry map[string]reflect.Type // A map registrying the store types | ||||||||||||
| config AugustConfig // August configuration | ||||||||||||
| storage map[string]AugustStore // A map of all the stores | ||||||||||||
| storage map[string]*AugustStore // A map of all the stores | ||||||||||||
| eventFunc AugustEventFunc // A function to call when an event happens | ||||||||||||
| systemModCache []string // Every time we modify a file, we add info about it so that FSNotify doesn't trigger on it | ||||||||||||
| } | ||||||||||||
|
|
@@ -62,7 +62,7 @@ func Init() *August { | |||||||||||
| Format: "json", | ||||||||||||
| FSNotify: true, | ||||||||||||
| } | ||||||||||||
| storage := make(map[string]AugustStore) | ||||||||||||
| storage := make(map[string]*AugustStore) | ||||||||||||
|
|
||||||||||||
| a := &August{ | ||||||||||||
| storeRegistry: stores, | ||||||||||||
|
|
@@ -81,18 +81,45 @@ func (a *August) Verbose() { | |||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // 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() | ||||||||||||
| log.Printf("Setting config: %s to %v", k, v) | ||||||||||||
|
|
||||||||||||
| if k == Config_Verbose && v.(bool) { | ||||||||||||
| // set verbose mode if we configure that | ||||||||||||
| a.Verbose() | ||||||||||||
| switch k { | ||||||||||||
| case Config_StorageDir: | ||||||||||||
| val, ok := v.(string) | ||||||||||||
| if !ok { | ||||||||||||
| return fmt.Errorf("config option %s requires a string value", k) | ||||||||||||
| } | ||||||||||||
| a.config.StorageDir = val | ||||||||||||
| case Config_Verbose: | ||||||||||||
| val, ok := v.(bool) | ||||||||||||
| if !ok { | ||||||||||||
| return fmt.Errorf("config option %s requires a bool value", k) | ||||||||||||
| } | ||||||||||||
| a.config.Verbose = val | ||||||||||||
| if val { | ||||||||||||
| a.Verbose() | ||||||||||||
| } | ||||||||||||
| case Config_Format: | ||||||||||||
| val, ok := v.(string) | ||||||||||||
| if !ok { | ||||||||||||
| return fmt.Errorf("config option %s requires a string value", k) | ||||||||||||
| } | ||||||||||||
| a.config.Format = val | ||||||||||||
| case Config_FSNotify: | ||||||||||||
| val, ok := v.(bool) | ||||||||||||
| if !ok { | ||||||||||||
| return fmt.Errorf("config option %s requires a bool value", k) | ||||||||||||
| } | ||||||||||||
| a.config.FSNotify = val | ||||||||||||
| default: | ||||||||||||
| return fmt.Errorf("unknown config option: %s", k) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| reflect.ValueOf(&a.config).Elem().FieldByName(k.String()).Set(reflect.ValueOf(v)) | ||||||||||||
| log.Printf("Config: %+v", a.config) | ||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (a *August) SetEventFunc(f AugustEventFunc) { | ||||||||||||
|
|
@@ -129,13 +156,12 @@ func (a *August) Unmarshal(input []byte, output interface{}) error { | |||||||||||
|
|
||||||||||||
| // Get a store by name. | ||||||||||||
| func (a *August) GetStore(name string) (*AugustStore, error) { | ||||||||||||
| a.mu.Lock() | ||||||||||||
| defer a.mu.Unlock() | ||||||||||||
| a.mu.RLock() | ||||||||||||
| defer a.mu.RUnlock() | ||||||||||||
| if store, ok := a.storage[name]; ok { | ||||||||||||
| return &store, nil | ||||||||||||
| } else { | ||||||||||||
| return &AugustStore{}, fmt.Errorf("data store %s not found", name) | ||||||||||||
| return store, nil | ||||||||||||
| } | ||||||||||||
| return nil, fmt.Errorf("data store %s not found", name) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // Register a store. | ||||||||||||
|
|
@@ -145,7 +171,7 @@ func (a *August) Register(name string, store interface{}) { | |||||||||||
| log.Printf("Registering store: %s of type %T", name, store) | ||||||||||||
|
|
||||||||||||
| a.storeRegistry[name] = reflect.TypeOf(store) | ||||||||||||
| a.storage[name] = AugustStore{ | ||||||||||||
| a.storage[name] = &AugustStore{ | ||||||||||||
| name: name, | ||||||||||||
| parent: a, | ||||||||||||
| data: make(map[string]AugustStoreDataset), | ||||||||||||
|
|
@@ -154,29 +180,38 @@ func (a *August) Register(name string, store interface{}) { | |||||||||||
|
|
||||||||||||
| // Populate registry is used during initial startup to load any existing data. | ||||||||||||
| func (a *August) populateRegistry(name string) error { | ||||||||||||
| a.mu.Lock() | ||||||||||||
| defer a.mu.Unlock() | ||||||||||||
| if _, ok := a.storeRegistry[name]; !ok { | ||||||||||||
| a.mu.RLock() | ||||||||||||
| _, ok := a.storeRegistry[name] | ||||||||||||
| store := a.storage[name] | ||||||||||||
| ext := "." + a.config.Format | ||||||||||||
| storageDir := a.config.StorageDir | ||||||||||||
| a.mu.RUnlock() | ||||||||||||
|
|
||||||||||||
| if !ok { | ||||||||||||
| return fmt.Errorf("store %s does not exists", name) | ||||||||||||
|
||||||||||||
| return fmt.Errorf("store %s does not exists", name) | |
| return fmt.Errorf("store %s does not exist", name) |
Copilot
AI
Mar 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing
Configto return anerroris a breaking API change for downstream users (previously it wasvoid). 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.