Skip to content
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

Fix: request counter race for concurrent requests matching the same interaction #46

Merged
merged 3 commits into from
Nov 15, 2023

Conversation

shreyagarge-f3
Copy link
Contributor

@shreyagarge-f3 shreyagarge-f3 commented Nov 14, 2023

If multiple requests matching the same interaction are made concurrently, we have a race condition where the latter requests can update the request counter before the former requests read them, giving the wrong count for the former requests. If we then also have a modifier based on attempt number, the modifier gets wrongly applied to the former requests.

This is elaborated further using this sequence diagram:

sequenceDiagram
    autonumber
    participant G as request 1
    participant R as Interaction
    participant A as request 2
    note over R: requestcount = 0
    activate G
    note over G: Match Interaction
    rect rgb(100, 100, 150)
        Note over G, R: lock interaction
        G -->> R: requestCount 0 -> 1
        Note over G, R: release lock 
    end

    note over R: requestCount = 1
    activate A
    note over A: Match Interaction
    rect rgb(100, 100, 150)
        Note over A, R: lock interaction
        A -->> R: requestCount 1 -> 2
        Note over A, R: release lock
    end
  
    note over R: requestCount = 2
    note over G: Modify/Write Response
    G -->> R: getRequestCount
    R ->> G: requestCount = 2
    deactivate G
    note over A: Modify/Write Response
    A -->> R: getRequestCount
    R ->> A: requestCount = 2
    deactivate A
Loading

To fix this we create a local copy of matched interactions and request count instead of the interaction at it's address directly

For each interaction, we keep track of the number of times it has been
called.
That counter can be used in the modifiers to change the response
returned to the client. For example it is possible to change the return
status on the 2nd invocation of a specific interaction.

The code has a bug when concurrent requests happen for the same
interaction.
The change modifies the way the concurrency is managed just for the
request counter, using channels instead of Lock.

As part of this change, we have added a new test that can be used to
prove that the original code has the issue and the proposed version
fixes it.

Co-authored-by: Shreya Garge <[email protected]>
@shreyagarge-f3 shreyagarge-f3 requested a review from a team as a code owner November 14, 2023 16:22
@andrea-f3 andrea-f3 added the bug Something isn't working label Nov 14, 2023
@andrea-f3 andrea-f3 marked this pull request as draft November 14, 2023 17:20
@andrea-f3
Copy link
Contributor

andrea-f3 commented Nov 15, 2023

closed #35 as duplicate

@andrea-f3 andrea-f3 closed this Nov 15, 2023
@andrea-f3 andrea-f3 reopened this Nov 15, 2023
@shreyagarge-f3 shreyagarge-f3 marked this pull request as ready for review November 15, 2023 09:48
Copy link
Contributor

@sofib-form3 sofib-form3 left a comment

Choose a reason for hiding this comment

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

Did you already test changes on some consumer? Happy to approve if you verified it all works good, but I would attempt writing a bit more reliable tests.


func (s *ConcurrentProxyStage) the_second_user_response_should_have_a_modified_body() *ConcurrentProxyStage {
c := 0
for _, res := range s.userResponses {
Copy link
Contributor

Choose a reason for hiding this comment

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

could we try to make tests a bit more reliable, for example:

  • verify that there is exactly 2 responses beforehand
  • verify body and status code of both responses, one of them being modified the other one not

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

func (s *ConcurrentProxyStage) the_concurrent_requests_are_sent_with_attempt_based_modifier() {
err := s.pact.Verify(func() (err error) {
attempt := 2
s.proxy.ForInteraction(postNamePactWithAnyName).AddModifier("$.status", fmt.Sprintf("%d", http.StatusConflict), &attempt)
Copy link
Contributor

Choose a reason for hiding this comment

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

Jjust wondering, is it out of scope to verify adding different modifiers on different attempts within same test?
like on 2nd attempt 409 and body1 on 3rd 418 and body2
not sure there is a use case but would be a valid test to verify interactions are correctly modified, if it is out of scope, feel free to ignore

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sofib-form3 We are doing this in a different branch. Will raise a PR soon.

Co-authored-by: Andrea Rosa <[email protected]>
@shreyagarge-f3
Copy link
Contributor Author

Did you already test changes on some consumer? Happy to approve if you verified it all works good, but I would attempt writing a bit more reliable tests.

We have tested it in a consumer, yes

Copy link
Contributor

@sofib-form3 sofib-form3 left a comment

Choose a reason for hiding this comment

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

Thanks! 👍

@shreyagarge-f3 shreyagarge-f3 merged commit 83a4313 into master Nov 15, 2023
2 checks passed
@shreyagarge-f3 shreyagarge-f3 deleted the sg-ar-concurrency branch November 15, 2023 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants