-
Notifications
You must be signed in to change notification settings - Fork 78
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
Package Syncing #118
Package Syncing #118
Conversation
Codecov Report
@@ Coverage Diff @@
## main #118 +/- ##
==========================================
+ Coverage 74.89% 76.22% +1.33%
==========================================
Files 22 22
Lines 1629 1632 +3
==========================================
+ Hits 1220 1244 +24
+ Misses 307 292 -15
+ Partials 102 96 -6
Continue to review full report at Codecov.
|
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.
@kpratyus Thanks for the PR!
Is it possible to add a test that tries to race (e.g. quickly return 2 successive PackagesAvailable offers from the Server to trigger 2 concurrent sync operations) and demonstrates that this fix works correctly?
@@ -32,6 +32,7 @@ var ( | |||
// It is safe to call methods of this struct concurrently. | |||
type ClientSyncedState struct { | |||
mutex sync.Mutex | |||
packageSyncMutex sync.Mutex |
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.
I don't think this is the right place for this mutex. The ClientSyncedState struct has a specific purpose that is described above and adding a mutex for packager syncer does not fit that purpose.
receivedProcessor
may be a better place for this mutex.
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.
The receivedProcessor
is constructed in the constructor of wsReceiver
. As the mutex has to be unique for each client and the pointer has to be passed to `receivedProcessor'. So mutex has to be included in the constructor of client.common, wsReceiver and receivedProcessor. To avoid these many changes I added the mutex in ClientSyncedState. What's your opinion on this?
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.
There is one-to-one relationship between Client, ClientSyncedState and receivedProcessor, so I don't understand where do you see a problem. A mutex declared in receivedProcessor struct will be unique for each client, just like it is unique when it is in ClientSyncedState. There is no need to pass the mutex to receivedProcessor, receivedProcessor can contain the mutex.
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.
Correct me if I am wrong here. For every client The function runOneCycle
runs in a loop. Everytime the function runs it creates a new receiver object.
r := internal.NewWSReceiver( c.common.Logger, c.common.Callbacks, c.conn, c.sender, &c.common.ClientSyncedState, c.common.PackagesStateProvider, c.common.Capabilities, )
This in turn creates a new receivedProcessor' object everytime.
processor: newReceivedProcessor(logger, callbacks, sender, clientSyncedState, packagesStateProvider, capabilities)`
So how is there a one to one relationship between client and receivedProcessor?
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.
You are absolutely right and I was wrong! Due to reconnections receivedProcessor may be re-created while a previous sync is still happening. So, receivedProcessor is the wrong owner for the mutex, you are right.
I still don't like putting it in ClientSyncedState, it doesn't fit the purpose.
Here are a couple options:
- Extend the purpose of ClientSyncedState to contain all of the client state. In that case the mutex can be a good fit there. It probably needs to be renamed to ClientState. I am not entirely sure this is a good idea though, since we are not really putting the entire client state in it, so it may become misleading.
- Another good owner for the mutex is ClientCommon. However, this will require passing the mutex where receivedProcessor is created and store the pointer to the mutex in receivedProcessor. Probably a bit awkward but doable.
Maybe there is another option that I haven't thought of, please feel free to suggest.
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.
While raising the PR I also thought of these 2 options. Code clarity wise adding all the mutexes in a single structure(ClientSyncedState here) makes sense but I totally get the point that we want to keep ClientSyncedState as lightweight as possible.
Although awkward I think keeping it in ClientCommon is a better option. In future if there arises need of any other locks then it would be totally extendable as well.
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.
Although awkward I think keeping it in ClientCommon is a better option. In future if there arises need of any other locks then it would be totally extendable as well.
Sounds good, let's go with this approach.
@@ -98,7 +99,7 @@ func (s *packagesSyncer) initStatuses() error { | |||
} | |||
|
|||
// doSync performs the actual syncing process. | |||
func (s *packagesSyncer) doSync(ctx context.Context) { | |||
func (s *packagesSyncer) doSync(ctx context.Context, mutex *sync.Mutex) { | |||
hash, err := s.localState.AllPackagesHash() |
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.
Why isn't the mutex locked right from the top of doSync? AllPackagesHash
is also susceptible to the race. It reads data that is written by SetAllPackagesHash
below.
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.
Ah, Missed that. I actually thought that it only handles logging. Will change the mutex. Thanks for pointing it out
I will try to add this test |
This PR revives work done in previous PRs (#118, #120) to make sure that only a single package syncing operation is ever in flight and also adds a test. The previous PRs did not account for needing to also protect `initStatuses` and `SetPackageStatuses`, so that's why the Lock and Unlock statements are not just paired in doSync. If you think the intent would be clearer using a sync.WaitGroup, let me know. The new test makes sure that the mutex correctly protects the local storage; if we comment out the calls to Lock/Unlock and run the test with the `-race` flag we can see the race condition taking place <details> ``` # Without using the mutex we can see the race condition of messages sent in parallel $ go test -run=TestPackageUpdatesInParallel -v -race -count=1 === RUN TestPackageUpdatesInParallel ================== WARNING: DATA RACE Write at 0x00c00003cdb0 by goroutine 10: github.com/open-telemetry/opamp-go/client/internal.(*InMemPackagesStore).SetLastReportedStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/inmempackagestore.go:95 +0x34 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).reportStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:335 +0x78 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).syncPackage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:199 +0x4dc github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).doSync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:132 +0x3b0 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync.gowrap1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x4c Previous read at 0x00c00003cdb0 by goroutine 8: github.com/open-telemetry/opamp-go/client/internal.(*InMemPackagesStore).LastReportedStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/inmempackagestore.go:91 +0x30 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).initStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:88 +0x64 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:59 +0x78 github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:185 +0x68 github.com/open-telemetry/opamp-go/client/types.CallbacksStruct.OnMessage() /Users/tpaschalis/GitRepos/opamp-go/client/types/callbacks.go:161 +0x84 github.com/open-telemetry/opamp-go/client/types.(*CallbacksStruct).OnMessage() <autogenerated>:1 +0x20 github.com/open-telemetry/opamp-go/client/internal.(*receivedProcessor).ProcessReceivedMessage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/receivedprocessor.go:160 +0xe94 github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func2() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:197 +0x4d0 Goroutine 10 (running) created at: github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x15c github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:185 +0x68 github.com/open-telemetry/opamp-go/client/types.CallbacksStruct.OnMessage() /Users/tpaschalis/GitRepos/opamp-go/client/types/callbacks.go:161 +0x84 github.com/open-telemetry/opamp-go/client/types.(*CallbacksStruct).OnMessage() <autogenerated>:1 +0x20 github.com/open-telemetry/opamp-go/client/internal.(*receivedProcessor).ProcessReceivedMessage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/receivedprocessor.go:160 +0xe94 github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func3() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:216 +0x4d0 Goroutine 8 (finished) created at: github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:196 +0x4b0 testing.tRunner() /opt/homebrew/Cellar/go/1.22.2/libexec/src/testing/testing.go:1689 +0x180 testing.(*T).Run.gowrap1() /opt/homebrew/Cellar/go/1.22.2/libexec/src/testing/testing.go:1742 +0x40 ================== ================== WARNING: DATA RACE Write at 0x00c0000999e0 by goroutine 11: runtime.mapaccess2_faststr() /opt/homebrew/Cellar/go/1.22.2/libexec/src/runtime/map_faststr.go:108 +0x42c github.com/open-telemetry/opamp-go/client/internal.(*InMemPackagesStore).CreatePackage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/inmempackagestore.go:50 +0x74 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).syncPackage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:203 +0x528 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).doSync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:132 +0x3b0 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync.gowrap1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x4c Previous read at 0x00c0000999e0 by goroutine 10: runtime.mapdelete() /opt/homebrew/Cellar/go/1.22.2/libexec/src/runtime/map.go:696 +0x43c github.com/open-telemetry/opamp-go/client/internal.(*InMemPackagesStore).Packages() /Users/tpaschalis/GitRepos/opamp-go/client/internal/inmempackagestore.go:36 +0x64 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).deleteUnneededLocalPackages() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:303 +0x58 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).doSync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:125 +0x1b4 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync.gowrap1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x4c Goroutine 11 (running) created at: github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x15c github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:185 +0x68 github.com/open-telemetry/opamp-go/client/types.CallbacksStruct.OnMessage() /Users/tpaschalis/GitRepos/opamp-go/client/types/callbacks.go:161 +0x84 github.com/open-telemetry/opamp-go/client/types.(*CallbacksStruct).OnMessage() <autogenerated>:1 +0x20 github.com/open-telemetry/opamp-go/client/internal.(*receivedProcessor).ProcessReceivedMessage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/receivedprocessor.go:160 +0xe94 github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func2() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:197 +0x4d0 Goroutine 10 (running) created at: github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x15c github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:185 +0x68 github.com/open-telemetry/opamp-go/client/types.CallbacksStruct.OnMessage() /Users/tpaschalis/GitRepos/opamp-go/client/types/callbacks.go:161 +0x84 github.com/open-telemetry/opamp-go/client/types.(*CallbacksStruct).OnMessage() <autogenerated>:1 +0x20 github.com/open-telemetry/opamp-go/client/internal.(*receivedProcessor).ProcessReceivedMessage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/receivedprocessor.go:160 +0xe94 github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func3() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:216 +0x4d0 ================== ================== WARNING: DATA RACE Write at 0x00c00003cdb0 by goroutine 11: github.com/open-telemetry/opamp-go/client/internal.(*InMemPackagesStore).SetLastReportedStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/inmempackagestore.go:95 +0x34 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).reportStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:335 +0x78 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).syncPackage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:229 +0x780 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).doSync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:132 +0x3b0 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync.gowrap1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x4c Previous write at 0x00c00003cdb0 by goroutine 10: github.com/open-telemetry/opamp-go/client/internal.(*InMemPackagesStore).SetLastReportedStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/inmempackagestore.go:95 +0x34 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).reportStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:335 +0x78 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).syncPackage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:229 +0x780 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).doSync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:132 +0x3b0 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync.gowrap1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x4c Goroutine 11 (running) created at: github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x15c github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:185 +0x68 github.com/open-telemetry/opamp-go/client/types.CallbacksStruct.OnMessage() /Users/tpaschalis/GitRepos/opamp-go/client/types/callbacks.go:161 +0x84 github.com/open-telemetry/opamp-go/client/types.(*CallbacksStruct).OnMessage() <autogenerated>:1 +0x20 github.com/open-telemetry/opamp-go/client/internal.(*receivedProcessor).ProcessReceivedMessage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/receivedprocessor.go:160 +0xe94 github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func2() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:197 +0x4d0 Goroutine 10 (running) created at: github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x15c github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:185 +0x68 github.com/open-telemetry/opamp-go/client/types.CallbacksStruct.OnMessage() /Users/tpaschalis/GitRepos/opamp-go/client/types/callbacks.go:161 +0x84 github.com/open-telemetry/opamp-go/client/types.(*CallbacksStruct).OnMessage() <autogenerated>:1 +0x20 github.com/open-telemetry/opamp-go/client/internal.(*receivedProcessor).ProcessReceivedMessage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/receivedprocessor.go:160 +0xe94 github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func3() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:216 +0x4d0 ================== ================== WARNING: DATA RACE Write at 0x00c00003cdb0 by goroutine 10: github.com/open-telemetry/opamp-go/client/internal.(*InMemPackagesStore).SetLastReportedStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/inmempackagestore.go:95 +0x34 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).reportStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:335 +0x78 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).doSync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:151 +0x69c github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync.gowrap1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x4c Previous write at 0x00c00003cdb0 by goroutine 11: github.com/open-telemetry/opamp-go/client/internal.(*InMemPackagesStore).SetLastReportedStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/inmempackagestore.go:95 +0x34 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).reportStatuses() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:335 +0x78 github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).doSync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:151 +0x69c github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync.gowrap1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x4c Goroutine 10 (running) created at: github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x15c github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:185 +0x68 github.com/open-telemetry/opamp-go/client/types.CallbacksStruct.OnMessage() /Users/tpaschalis/GitRepos/opamp-go/client/types/callbacks.go:161 +0x84 github.com/open-telemetry/opamp-go/client/types.(*CallbacksStruct).OnMessage() <autogenerated>:1 +0x20 github.com/open-telemetry/opamp-go/client/internal.(*receivedProcessor).ProcessReceivedMessage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/receivedprocessor.go:160 +0xe94 github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func3() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:216 +0x4d0 Goroutine 11 (running) created at: github.com/open-telemetry/opamp-go/client/internal.(*packagesSyncer).Sync() /Users/tpaschalis/GitRepos/opamp-go/client/internal/packagessyncer.go:69 +0x15c github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func1() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:185 +0x68 github.com/open-telemetry/opamp-go/client/types.CallbacksStruct.OnMessage() /Users/tpaschalis/GitRepos/opamp-go/client/types/callbacks.go:161 +0x84 github.com/open-telemetry/opamp-go/client/types.(*CallbacksStruct).OnMessage() <autogenerated>:1 +0x20 github.com/open-telemetry/opamp-go/client/internal.(*receivedProcessor).ProcessReceivedMessage() /Users/tpaschalis/GitRepos/opamp-go/client/internal/receivedprocessor.go:160 +0xe94 github.com/open-telemetry/opamp-go/client/internal.TestPackageUpdatesInParallel.func2() /Users/tpaschalis/GitRepos/opamp-go/client/internal/httpsender_test.go:197 +0x4d0 ================== testing.go:1398: race detected during execution of test --- FAIL: TestPackageUpdatesInParallel (0.10s) FAIL exit status 1 FAIL github.com/open-telemetry/opamp-go/client/internal 0.286s ``` </details> Fixes #84
Making sure that only one package syncing operation happens at a time. #84