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
Concurrency issues with specs2 3.8.7+ #556
Comments
The execution indeed changed between Also this might tell you that you have possible concurrency issues within your test code and/or production code and you might want to fix those if that's the case (in that case the bug will have become a feature :-)). |
The issue here is that it's unlikely to be concurrency at the code level. The Clojure loading block, for example, is synchronized… But somehow there has been a change in classloader behavior (I hypothesize this because the way I fixed it was to replace the context classloader with the current class's classloader at the beginning of the synchronized block and restore it after). It also seems reproducible on different boxes (i.e., it fails at the same point on different machines), which is very odd for something that could be race condition-y. I'll keep investigating, including trying that |
Thanks for investigating, sorry to dump this work on you. You rarely want to have to debug a test library :-(. |
Haha no it's fine. I'd also like to see if I can make a reproducible case, both for my own benefit and yours :) |
I just did some tests and I realize that there's a bit more concurrency in started test 1
started test 2
started test 3
started test 4
finished test 4 after 3 millis
finished test 2 after 52 millis
finished test 1 after 58 millis
finished test 3 after 86 millis whereas in
Maybe this added concurrency is causing the issue? I don't remember otherwise any classloading change. |
I still don't see why that would break things in a synchronized block, though… I'm also looking into |
I'm actually realizing that most if not all of these issues are happening during singleton initialization… So that's interesting. Not sure what it means yet though! EDIT: So that I'm not continually posting new comments, some more failures:
This second one is for a line of code:
Interestingly, moving away from the context class loader is precisely what fixed the Clojure situation as well. The first one also looks like it could be related to classloading, though more tangentially. Currently my only conclusion is: something funkay this way comes! EDIT2: I've also tried bumping from sbt 0.13.11 to 0.13.13 to no avail. EDIT3: also of note, the above ^^ are completely independent code paths, and are hit in different specs. Obviously the stuff happening deep in akka-land ain't our code :) |
Unbatched doesn't seem to help. Is there something I can print/check from inside a spec to make triple-plus sure that I've set unbatched correctly? |
Oh. Oh dear. Okay, I've found the issue. I added this to the Clojure initialization stuff:
The context classloader is a This is actually very helpful; I think it's going to help me create a reproducible test case. More to come. |
This looks very puzzling, I hope you will get to the bottom of it :-). |
Well, the |
Okay, minimal test case lives at https://github.com/Shadowfiend/specs2-concurrent-boom-boom . 46 LOC. Would it make sense to refer this to the sbt folks/take it to a mailing list/something else? |
Allllll righty, I did some bisecting. In the process I found that the classloader behavior is indeed somewhat intermittent, though not strongly so. With a reasonable measure of certainty, it looks like commit ffa0a7b is the culprit. What in that commit caused it… Is hard to tell, and I'm out of blueberries for the day. |
@etorreborre Is it possible that the new execution strategy is using a different thread pool provided by sbt? That is, that it is using threads that aren't spawned directly from within specs2? |
specs2 should be using its own thread pool. I can check later if anything changed around that. |
Another piece that would be useful in investigating this is where that thread pool comes from---i.e., where is it spawned and where do new threads get added to that pool? If you could give me a pointer to that, it could help, as there are quite a few abstraction layers to descend through and I'm having trouble digging through them. Otherwise I'll post more on here when I manage to figure it out :) |
So, after some investigation on the sbt group and flat out asking and being pointed to some known classloader issues they're looking at, I thought I'd try to see if I could take a quick stab at just setting the right context classloader within specs2 so it works for everything. I added some context classloader setup in For reference, the code after looks like:
|
I am also not familiar with classloaders and I don't know if such a modification would be safe for all users, especially when not using sbt. One thing you can try "locally" is to mix-in a trait to your specs: trait WithClassClassLoader extends AroundEach {
def around[R : AsResult](r: =>R): Result = {
val old = Thread.currentThread().getContextClassLoader()
try {
val result = r
Thread.currentThread().setContextClassLoader(result.getClass.getClassLoader())
AsResult(result)
} finally Thread.currentThread().setContextClassLoader(oldContextClassLoader)
}
} I am actually not sure how this works. If your test code throws an Exception because it is not executed with the right class loader how will we reach the point where we set the right classloader? (note that in your version of |
Maybe this class loader is the one which should be set as the context class loader before running the tests? |
I'll give that classloader a shot and report back.
I noticed this after posting my comment yesterday… Is it possible
Well what I can say for sure is that after the commit I pointed out above, specs2 isn't safe for all users (particularly using sbt in a multi-project setup). Definitely the potential for a catch-22 here if we fix it and break other situations, though... I've posted a question on Stack Overflow to see if anyone has any ideas at the sbt level. |
So what I gather from this thread is that the classloader used to execute the tests is not deterministic and is influenced by how we deal with concurrent execution. For example in the commit you point out we introduce a bit of scheduling to be able to time out tests. I used your "boom-boom" project a bit and it seems that immutable specifications and just one module is enough to reproduce the issue. Also I tried to reproduce the same case inside specs2 which is also a multi-module build and I don't get this issue. In fact it seems that there is no difference between the class loaders, they are both I need to play around more in order to understand what's going on. I'll try to do more of that this week. |
Your understanding seems the same as mine, yep. Very weird that specs2 doesn't exhibit the same issue. Sorry I haven't been able to circle back here, a few other things have kept me distracted and will probably continue to keep me busy for a few days; when I get a chance, though, I'll be happy to do more spelunking as needed. |
Hey guys! At the risk of turning this into a "me too" thread, we've hit this issue within a large scala codebase trying to upgrade to Specs2 3.8.9. I went through the same rough process as @Shadowfiend and came to find that it's definitely introduced somewhere in 3.8.7 - that is, going back to 3.8.6 makes the issues go away completely. I don't think it'd be particularly easy for me to construct a reproducible test case better than @Shadowfiend, but if there's anything I can do to help or provide more details I'm at your disposal! 👍 |
@nathankleyn that's definitely good to know, that means I must shift my priorities :-). |
Can you please try |
@etorreborre No worries, can do - and many thanks for the quick reply! Still waiting for it to appear in Maven Central right now, no sign just yet. |
Something was interrupted in the publishing. I'm now trying to publish |
Ok done |
I'm closing this issue as it should be ok now. Please reopen if necessary. |
Hey @etorreborre! I can confirm it seems to fix the issues on my end. Thanks so much for the fix! 👍 |
Haven't confirmed it on our end yet but I'm hopeful. Appreciate the effort! |
Yep, confirmed that this fixed the classloader issues we were having! Thanks! |
I encountered the exact same issue when migrating to play 2.6. When using |
@alexduf can you try with |
You can also try to pass sbt> testOnly *Spec -- useCustomClassLoader I'm afraid that the real problem lies in sbt but I'm not sure. |
Hi @etorreborre, since I'm using Play I'm stuck with whatever version they use (3.8.9 as far as I know) I tried using the option with 3.8.9 to no avail, so I assumed it was added later? I'll stay on |
On Mockito side this might be due to the Mockito version you are using, I think I did an upgrade to the latest in 4.x. |
…s2 version above play's to pull in fix for etorreborre/specs2#556
If anyone stumbles on this bug with something similar, I've sorted my issue by using |
We recently moved to specs2 3.8.8 and started seeing a weird situation where the classloader used in internal code that bootstraps a Clojure environment was behaving differently from before. We narrowed down the version of specs2 that started causing this to version 3.8.7. I implemented a workaround for this, but… We then started seeing these:
Unfortunately, these don't happen in 3.8.6---only 3.8.7 and 3.8.8. So it seems like something in the 3.8.6->3.8.7 got weird with respect to classpaths and possibly class loaders, though I don't know what it would be. Notably, if I run the offending specs individually, they work fine. But if I run them in a chunk (we have multiple subprojects and run their tests using
all common/test universe/test portal/test fabric/test persistence/test rica-lib/test spandex/test
), not so much...Switching on the project down to 3.8.6 makes these pass, up to 3.8.7 fails, down to 3.8.6 again passes.
I tried looking through the diff between the versions but nothing jumped out at me, so I'm down to my last resort… Any ideas?
The text was updated successfully, but these errors were encountered: