I was working on our testing framework at work and I added a feature to save requests. The goal was to allow the tester to make assertions on requests received by a mock server. The requirements were:
The shared boolean represents a global system state.
- The testing framework should support multiple threads enabling/disabling the feature independently.
- Threads must be coordinated to prevent one thread from disabling a flag while another depends on it.
The mock server received concurrent requests and needed to save them, so I added this state (the flag) to the mock server to enable the save-request feature.
My first solution was to use an atomic boolean. I would then have situations like this:
- Thread A enables the flag (true) at timestamp 1:00
- Thread B enables the flag (true) at timestamp 1:05
- Thread A disables the flag (false) at timestamp 1:10
- Thread B is still running and thinks the flag is enabled
The issue is that when Thread B is done, it will try to get the saved requests but won't find any because the flag was disabled by Thread A. Point 3 of our requirements doesn't work here.
The issue with the boolean flag is that it doesn't allow us to know when all the threads are done. So instead of using a boolean, we will use a counter.
So every time a thread wants to save requests, we will ask it to increment the counter. As several threads could access this counter, we will use an atomic counter. The flag will be disabled if the counter is equal to zero.
Let's go back to our scenario now:
- Thread A increments the counter to 1 at timestamp 1:00
- Thread B increments the counter to 2 at timestamp 1:05
- Thread A decrements the counter to 1 at timestamp 1:10
- Thread B is still running; the counter value is 1 and requests are being logged.
Implementation example in main.go. And here is the output
[G1] Starting my work and enabling the flag
[G1] Flag is enabled
[G2] Starting my work and enabling the flag
[G1] I'm done let's disable the flag
[G2] Oh no, flag is disabled
[G2] I'm done let's disable the flag
[GM] Final flag value: false
-----
[G1] Starting my work and enabling the flag
[G2] Starting my work and enabling the flag
[G1] Flag is enabled
[G1] I'm done let's disable the flag
[G2] Flag is true
[G2] I'm done let's disable the flag
[GM] Final flag value: 0