-
Notifications
You must be signed in to change notification settings - Fork 265
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
Intermittent AmbiguousArgumentsException when running tests in parallel #260
Comments
Thanks for this report. I'm not sure what's happening here. Argument specs are queued and retrieved using threadlocal storage, so I'm not entirely sure the threading is the problem. If you run it single-threaded do you sometimes get the issue? I have a suspicion that there is a test that is queuing up an arg matcher that under certain orderings is causing this problem. (Does xunit randomise the order of tests? It might be worth checking which test precedes the broken one in a test runner log.) |
Thank you for the attention.
Thanks, |
For 5 I think the answer is "yes". For 4 I think it should be ok unless async code is accessing the substitute at the same time as another thread is trying to configure it or assert on it (see #256 for example). For a call like
All the above steps should take place on a single thread, as they rely on threadlocal storage. Other threads running at the same time and configuring other substitutes should (as far as I can tell) be ok. It does mean there could be a problem if substitute instances are shared between threads. If a substitute is being configured/asserted on at the same time as production code (or other test code) is accessing the substitute then step 3 might look up the last recorded call, and find Another thing to check is anywhere you are substituting for classes. If you have accidentally used an arg matcher with a non-virtual method that can cause these problems. As an aside, you don't need to use
This won't help cases where you need a mix of Finally, I'm really sorry for the troubles you are experiencing here. These are terrible cases to debug. 😞 |
@dtchepak BTW, isn't that something that NSubstitute should handle? Of course, it might be out of scope that we setup fixture and use it concurrently (it's supposed that it's configured and then is used). However, given the fact that we heavily rely on previous call information, shouldn't we ensure that NSubstitute doesn't fail for such scenario? My concern is that if we have solution with 2k tests, it might be a nightmare to find exact place where you configure and use substitute concurrently (and sometimes even not possible to avoid that, if it's used in constructor e.g.). I could try to fix that by storing a copy of "last call information" in thread local storage, rather than rely on shared state. Of course, we should be veeeery careful to not break anything, but as far as I can see, not too much places should be adjusted. I'm interested in this because in our project we also use xUnit and I also saw concurrency issues. I want NSubstitute to be multi-threading resistant as much as possible. P.S. It would be a cool change for v 2.0 😉 |
@zvirja Happy to give it a try. :) I've got a few thousand tests I can try it out on to test for regressions. :) Btw, we don't need to rush things out for a particular release. I'm happy to release whenever a new feature hits (with a little delay to let me test out the version on a few test suites.) |
@dtchepak The reason why I want that for 2.0 is that it might bring some potential breaking changes, so it would be much expected. But I hope to avoid them :) |
@dtchepak @zvirja Thank you very much for your comments, guys. It would be awesome to have it fixed for a future release. I don't feel confident to make changes to NSubstitute myself for now, but I'll be glad to run my current tests on top of a beta version to help testing. Even though it seems like a problem I cannot fix myself , your comments will certainly help me take measures to avoid the problem. For example, removing unnecessary Args from the test base greatly reduced the frequency of failures already. A simple measure that would help a lot in detecting the issue would be to provide more info in the AmbiguousArgumentsException of what Args were being matched to which method, and why is it ambiguous. If we had a log of the stack built into the thread storage it would also be helpful to track down the source of the problem. I don't know, however, how much impact that could have in performance. |
@luciobemquerer: I like the idea of adding more info to the Added #262 to track this. |
P.S. Reply is a "long-read", but please read it and try the suggested diagnostics. I've thought more about this and wasn't able to imagine the scenario how that could happen due to concurrency WITH valid usage. Specs are pushed to thread local storage, therefore other threads would not affect that. Also, this issue happens before the Now let's analyze the signature: TGrainInterface GetGrain<TGrainInterface>(long primaryKey, string grainClassNamePrefix = null) Issue cannot happen for the Arg.Any<string>();
factory.GetGrain<IValueSource>(Arg.Any<long>()).ReturnsForAnyArgs(valueSource); You might note, if there is any specification is present in the queue before current invocation and it isn't for Likely, happened something @dtchepak mentioned above:
I would imagine that something like following happens: subs.NonVirtualMethod(Arg.Any<non-long-type>()); Thread code now: factory.GetGrain<IValueSource>(Arg.Any<long>()).ReturnsForAnyArgs(valueSource); In this case thread still "remember" previous specification queued and we cannot fix that somehow. I'm currently working on improving NSubstitute for concurrency, so it would be useful to investigate why that happens. To do that I would like to ask you to run the diagnostics code and provide us with issue root cause. Steps:
Let us know whether that was a mistake in your code our whether this is NSubstitute issue. Thank you a lot for your efforts! :) |
@zvirja: yes i think it should fail fast there. As an aside, I've always wanted to completely rip out the arg matching stuff and replace it with something that evaluates all the possible combinations and throws if there is more than one. (I don't think it would be any slower than the current implementation.) Never got around to it though because the current one works so not sure it is worth the time. |
@zvirja, thanks a lot for the attention. I managed to reproduce the error while using the diagnostic tool you suggested. I'm outputting logs to a file with the following implementation:
I captured the same error two times. EXAMPLE 1
The line where the exception is thrown is the same I mentioned in the opening of this issue.
Obviously, the Int64 matcher is the The TagSuggestQuery matcher is coming from another test, called
In the line above,
The signature of ExecuteQuery is:
EXAMPLE 2
Exception is thrown is the same line as the previous example, and the Int64 matcher is the The NotificationListQuery matcher is coming from another test, called
In the line above, The signature of ExecuteQuery is the same as above:
CONCLUSION
I hope this information help you guys to identify the problem. Please let me know if you need more info. |
@luciobemquerer HUGE thanks for your try and for such a verbose reply! Therefore, I would like you to give it another try.
Also, I would like to ask you to repeat your test session again using the updated diagnostics. I've extended logging, so please log all the properties. If that might be possible, please provide me with listings for both tests (failing and enqueuing ones). It could happen that some important detail eludes us. However, if that is not possible - provide us with results like you did in your previous reply. Thank you in advance. In the meanwhile I'll continue to think about that and imagine how that could happen... |
@zvirja I believe I finally spotted the problem. The item 2 in your previous response called my attention to it, because the method being called was Consider the following code:
The problem happens like this:
The code bellow will reproduce the error consistently:
In conclusion:
Once more, thank you very much for all the support. Please let me know if you need more info. |
Please don't apologise. It's NSubstitute's job to make your life easier, this is one area where it has definitely failed you. Sorry for the hassle it's caused. And thanks for going to the effort to collect information for us and for posting the source of the problem. @zvirja, thanks so much for your efforts with this task too! |
@luciobemquerer Cool, so happy to figure out the root cause of the issue. I wasn't able to stop thinking about this, because it was really curious issue ;) Now I can have a rest 😫 I'd like to thank you for your involvement and for the job you did from your side. It would be just one more unresolved issue without your investigation! I suspected that it's something task related (because they were present in both cases), but the whole picture appeared to be a combination of a few places. Really interesting case :) As for the solution here, it becomes complicated. I don't see how we could avoid it gracefully. When we call
There is such an API currently. Just call What we could really do, is the following:
Thank you all for this collaboration 😉 |
Hi @luciobemquerer, #262 is still open to improve the message for this case. |
Hi, I am receiving a similar issue using v3.1.0. For example, I may have a substitution that looks like this: But I receive an AmbiguousArgumentsException thrown from within NonParamsArgumentSpecificationFactory. I've downloaded the source for 3.1.0 and attached the debugger inside NonParamsArgumentSpecificationFactory. What I noticed is that when the AmbiguousArgumentsException is thrown, suppliedArgumentSpecifications contains an extra argument that has appeared out of nowhere. The behaviour is random, and the argument could be anything. It has obviously leaked across from another test. |
@cbp123 The arg matchers are threadlocal, so it could be added by whatever test is run previously on that thread. Is it possible to log/find out what test is being run on that same thread immediately prior to this one? My suspicion is that an arg matcher is added to a non-virtual call or not as part of a call specification in another test, and that depending on execution order it sometimes will cause a problem. If you are able to try the current repository head it has some improved detection for these cases that may make it easier to track down. (Otherwise it will be in v4.0) |
Hi, I have a similar problem, I think, but I'm a real novice, so might just be doing things thw wrong way. I consistently receive the ambiguity exception when running these two tests (located in separate test classes:
It's the second test that throws the exception at the indicated point. For the first test, For the second test, the class I want to test is The second test succeeds if I run it by itself or in combination with all other tests in the solution, but fails as soon as the first test is also included in the same test run. AM I doing something inappropriate in any of these tests? How can I change the tests to avoid the problem? |
Hi @krilbe, Please ensure the If you install NSubstitute.Analyzers.CSharp into your test project it will help pick up cases likes this that can cause problems for NSubstitute. The Analyzers are still in beta so appreciate any feedback you can provide. Please let me know if that helps. I'm happy to look into this further if it is still not working for you! |
Thanks! Installed it and it shows the following messages. The interface and class Please notice the exception from the analyzer, code AD0001, at the first message line. I can't really see anything wrong with my code so far. |
This is the complete code for
|
Thanks @krilbe. @tpodolak, do you have any guesses about the exception @krilbe found in this comment? I'm also wondering if I need to update the messaging about "interface members" in the analyzers and in the docs as I think that has caused confusion in this case. 🤔 (cc: @zvirja ) |
Thanks! That solved the problem. Now I just have to decide if testability is worth making the methods virtual, which is not otherwise required. I also wonder why the warnings about internal type are there, considering I do have Also, the exception remains. |
In terms of avoiding putting I'm not sure why It is possible to manually suppress this message:
The manual step of creating the file is unfortunately necessary due to a Roslyn bug. We'll look into that and the exception. Thanks for the feedback! |
Thanks! Good work and very nice with the quick action! :-) The entire class is already "factored out" for testability, i.e. enabling DI for creation of instances, so yet another such step isn't 100 % appealing. :-) Anyway, I'll evaluate my options again and then decide. |
Hi @krilbe, regarding InternalsVisibleTo issue, did you place the attribute in the assembly which contains the internal type or in the test assembly? |
We have a set of 1.5k xUnit tests which heavily rely NSubstitute. When running all tests, xUnit executes tests in parallel, and we are frequently having a random one or two of them failing intermittently. If we run the tests separately, they succeed every time.
Tests runs don't fail every time, and it don't always fail with the same tests, but some tests are frequently failing for a time. When we add or remove tests from the code base, or when we try to fix the tests that are failing, usually the problem moves around, and start showing up in a different test.
Bottom line, the issue fluctuates randomly around our test base, and we could not confidently reproduce it, or identify the cause. We are using AutofacContrib.NSubstitute.AutoSubstitute to resolve dependencies.
One particular line that is failing often recently is the second one here:
The exception we receive is:
The signature of the GetGrain() method (from Microsoft.Orleans) is:
public TGrainInterface GetGrain<TGrainInterface>(long primaryKey, string grainClassNamePrefix = null) where TGrainInterface: IGrainWithIntegerKey
The text was updated successfully, but these errors were encountered: