Skip to content

Execute Mock response handlers un-synchronized#1885

Merged
leonard84 merged 4 commits into
spockframework:masterfrom
leonard84:desync-mock-actions
Feb 18, 2024
Merged

Execute Mock response handlers un-synchronized#1885
leonard84 merged 4 commits into
spockframework:masterfrom
leonard84:desync-mock-actions

Conversation

@leonard84

Copy link
Copy Markdown
Member

Prior to this commit the response handler of a mock was called in a
synchronized method. This could lead to deadlocks when one mock was
blocking in its response handler to wait on another mock.

fixes #583, #1882

@leonard84 leonard84 self-assigned this Feb 16, 2024
@leonard84 leonard84 requested review from a team and AndreasTu February 16, 2024 13:53
@codecov

codecov Bot commented Feb 16, 2024

Copy link
Copy Markdown

Codecov Report

Attention: 1 lines in your changes are missing coverage. Please review.

Comparison is base (2c7db77) 80.44% compared to head (8c9cb9a) 80.76%.
Report is 12 commits behind head on master.

Files Patch % Lines
...g/spockframework/mock/runtime/MockInteraction.java 93.75% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1885      +/-   ##
============================================
+ Coverage     80.44%   80.76%   +0.32%     
- Complexity     4337     4374      +37     
============================================
  Files           441      442       +1     
  Lines         13534    13744     +210     
  Branches       1707     1728      +21     
============================================
+ Hits          10888    11101     +213     
+ Misses         2008     2005       -3     
  Partials        638      638              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

AndreasTu
AndreasTu previously approved these changes Feb 16, 2024
Prior to this commit the response handler of a mock was called in a
synchronized method. This could lead to deadlocks when one mock was
blocking in its response handler to wait on another mock.

fixes spockframework#583
Comment thread docs/release_notes.adoc Outdated
@leonard84 leonard84 requested a review from AndreasTu February 18, 2024 20:03
@leonard84 leonard84 merged commit ce21741 into spockframework:master Feb 18, 2024
@leonard84 leonard84 deleted the desync-mock-actions branch February 19, 2024 09:01
@leonard84 leonard84 added this to the 2.4-M2 milestone Feb 22, 2024

@Vampire Vampire left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to immediately rollback this PR and release 2.4-M3. This majorly breaks interaction matching in a multi-threaded situation and does not remove the blocking that was intended to be removed at all, due to the locking that was introduced.

The test that was added is not meaningful, it will always succeed after waiting for 30 seconds. Before the MockInteraction was made thread-safe with locks, it finished fast but was also not meaningful as it would never fail. Using a lock-free thread-safe collection instead of locks or moving the respond call to outside the write lock would make the test succeed faster again but still never fail. Just fixing the test is not an option though, as the logic is majorly broken as race conditions are introduced due to the now missing synchronization that was removed.

If you for example have two interactions with 1 * for the get in the test, then it can easily happen, that the first thread uses the synchronized findInteraction which uses scope.match which uses interaction.isExhausted which uses acceptedInvocations.size().

Then before the first thread calls accept which would record the interaction invocation, the second thread does the same and gets the same interaction.

Now both threads call accept on the same interaction, where the first succeeds and the second throws a TooManyInvocationsError due to maxCount being exhausted, which is then swallowed in said test by the executor service and makes the test fail once it is rewritten to better test the situation.

Besides that, the accept in the anonymous class in InteractionScope.addInteraction probably has the same problems as MockInteraction had before it was made thread-safe due to reading and modifying state that is now no longer protected by the synchronization that was removed.

It probably should just be made illegal to block in one response handler waiting for another response handler.

Otherwise, someone has to come up with a clever change that fixes this while not breaking current logic like these changes. But I guess for this some major refactoring would be necessary, so that only the response handler call is somehow moved to after the synchronization. Maybe by returning the response handler from accept and then invoking it after the synchronization or something like that, if that works.

Also, this change, or a an according other fix would need to be listed as potentially breaking change, as before the response handlers were synchronized and thus were safe to modify state, while with the response handlers not being synchronized anymore this is no longer the case and users would have to care about thread-safety themselves.

Vampire added a commit that referenced this pull request Mar 6, 2024
Vampire added a commit that referenced this pull request Mar 10, 2024
Vampire added a commit that referenced this pull request Mar 16, 2024
leonard84 pushed a commit that referenced this pull request Mar 17, 2024
leonard84 added a commit that referenced this pull request Mar 17, 2024
This PR reverts the changes of PR #1885 and hopefully fixes #583, #1882,
and #1899 properly without the fallout of the previous PR.
All interaction handling stays synchronized, but the response generation
happens unsynchronized.

Co-authored-by: Leonard Brünings <leonard.bruenings@gradle.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Concurrent interactions deadlock because of a synchronized method MockController#handle

3 participants