-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat: Poller implementation periodically fetches workspace configs #2
Conversation
8013cf8
to
17465b6
Compare
87f77f5
to
8fa44bf
Compare
* Split workspace config model to multiple files * Explicitly name model version in package * Renamed SDK to Control Plane * Refactored sample app for best practices * Used url.URL to configure base URL for better error handling
* Split workspace config model to multiple files * Explicitly name model version in package * Renamed SDK to Control Plane * Refactored sample app for best practices * Used url.URL to configure base URL for better error handling
|
||
// Start starts the poller goroutine. It will poll for new workspace configs every interval. | ||
// It will stop polling when the context is cancelled. | ||
func (p *Poller) Start(ctx context.Context) { |
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.
[minor] Although we have a way to stop this goroutine, there is no way to know when it got terminated or not. This could be an issue in some cases.
I would suggest on of the following:
- Make
Start
a blocking method, calledRun(ctx context.Context)
- Introduce a background context, remove ctx from params and add a
Stop
method, that blocks until the go routine returns
} | ||
|
||
if err := p.handler(response); err != nil { | ||
return fmt.Errorf("failed to handle workspace configs: %w", 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.
[minor] To make the final error message more readable avoid including words like error
or failed
when wrapping and returning messages, as they will be redundant when wrapped multiple times.
return fmt.Errorf("failed to handle workspace configs: %w", err) | |
return fmt.Errorf("handling workspace configs: %w", err) |
p.log.Debug("polling for workspace configs") | ||
wcs, err := p.client.GetWorkspaceConfigs(ctx) | ||
if err != nil { | ||
return fmt.Errorf("failed to get workspace configs: %w", 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.
[minor]
return fmt.Errorf("failed to get workspace configs: %w", err) | |
return fmt.Errorf("get workspace configs: %w", err) |
} | ||
|
||
// remove deleted workspace configs (missing ids) | ||
currentIds := make([]string, 0, len(c.configs.Workspaces)) |
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.
[nit-pick] idiomatic way in go
currentIds := make([]string, 0, len(c.configs.Workspaces)) | |
currentIDs := make([]string, 0, len(c.configs.Workspaces)) |
return nil | ||
} | ||
|
||
return copyConfigs(c.configs) |
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.
We should consider the memory usage vs safety tread-offs of coping configs here.
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.
We should consider writing an integration level test, that would combine multiple components, using httptest
instead of mocks
c.updateLock.Lock() | ||
defer c.updateLock.Unlock() |
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.
We can use read locks here, since we are just reading from the configs.
c.updateLock.Lock() | |
defer c.updateLock.Unlock() | |
c.updateLock.RLock() | |
defer c.updateLock.RUnlock() |
c.subscriberLock.Lock() | ||
defer c.subscriberLock.Unlock() |
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.
We can use read locks here, since we are just reading from the slice.
c.subscriberLock.Lock() | |
defer c.subscriberLock.Unlock() | |
c.subscriberLock.RLock() | |
defer c.subscriberLock.RUnlock() |
for k, v := range c.SourceDefinitions { | ||
wc.SourceDefinitions[k] = v | ||
} | ||
|
||
for k, v := range c.DestinationDefinitions { | ||
wc.DestinationDefinitions[k] = v | ||
} | ||
|
||
for k, v := range c.Workspaces { | ||
wc.Workspaces[k] = v | ||
} |
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.
for k, v := range c.SourceDefinitions { | |
wc.SourceDefinitions[k] = v | |
} | |
for k, v := range c.DestinationDefinitions { | |
wc.DestinationDefinitions[k] = v | |
} | |
for k, v := range c.Workspaces { | |
wc.Workspaces[k] = v | |
} | |
maps.Copy(wc.SourceDefinitions, c.SourceDefinitions) | |
maps.Copy(wc.DestinationDefinitions, c.DestinationDefinitions) | |
maps.Copy(wc.Workspaces, c.Workspaces) |
for id, config := range configs.SourceDefinitions { | ||
c.configs.SourceDefinitions[id] = config | ||
} | ||
|
||
for id, config := range configs.DestinationDefinitions { | ||
c.configs.DestinationDefinitions[id] = config | ||
} |
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.
for id, config := range configs.SourceDefinitions { | |
c.configs.SourceDefinitions[id] = config | |
} | |
for id, config := range configs.DestinationDefinitions { | |
c.configs.DestinationDefinitions[id] = config | |
} | |
maps.Copy(c.configs.SourceDefinitions, configs.SourceDefinitions) | |
maps.Copy(c.configs.DestinationDefinitions, configs.DestinationDefinitions) |
defer c.subscriberLock.Unlock() | ||
|
||
subscriber := &Subscriber{ | ||
notifications: make(chan notifications.WorkspaceConfigNotification), |
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.
Should a buffered channel be more appropriate here? If the consumer of the channel is blocked for some reason, it is going to block all the consumers.
Description of the change
Implements background fetching of workspace configs. ControlPlane SDK now maintains a local workspace config cache, which is periodically updated by a background goroutine. More specifically:
Poller implementation
modelv2.WorkspaceConfigs
now exposes anUpdatedAt
function that returns the maximum updated at timestamp of included configs.WorkspaceConfigCache
ControlPlane.GetWorkspaceConfigs()
.ControlPlane
WithPollingInterval
to control Poller's interval. If set to 0, polling is disabled.GetWorkspaceConfigs
now fetches config from cache if polling is enabled. If not, it makes an http call using Client.Subscribe
.Note Poller and WorkspaceConfigCache have been implemented in different commits for easier review.
Notion link
Type of change
Related issues
Checklists
Development
Code review