-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
support for multi-threaded test-cases? #246
Comments
Hi Kosta, There's a few things in there.
|
Hey Phil,
|
'Ancient' it may be (although I don't know what that makes me) - but it's still very widely used in real-world production systems. |
For those needing a brutal hack job of a partially thread safe CATCH and are reading this issue, you may find my fork at https://github.com/ned14/Catch-ThreadSafe of use. I make no claims as to its reliability, but it works for me. |
Hi Phil, can you share anything about your "long term plans" for thread safety that you mentioned in your reply? I might be willing to contribute. The Catch-ThreadSafe fork created by ned14 seems to be a good starting point, but you seem to think that more is required to get this water tight. My goal would be to not do this as another fork, because I would rather not have to sync with the main branch all the time. If you could outline what you think would be needed to get this accepted into the main branch then I will try to contribute a corresponding patch. |
My thread safe fork has proven itself reliable under an awful lot of testing. However, it is a very nasty hack. I wouldn't do it that way in a million years if one had a choice. |
Well, it depends on what the goal is. I think the most important thing is to get this feature into the main branch, because this is the main thing that is holding Catch back (IMHO). Multithreaded code is becoming increasingly common (especially since C++11 provides standardized support) and it is a shame to have Catch disqualified for a lot of bigger projects because of this. It is a hard sell to tell developers that they cannot use some features of the standard C++ library because of the testing framework that was chosen. I honestly think that IF ned14s fork really has no problems (which I honestly cannot determine since I am too new to the inner workings of catch) then maybe it should simply be integrated into the main branch. Having better plans is nice, but the changes in the existing fork are relatively small and do not influence the public interface. Why not make integrate them as an option with a preprocessor switch (thus keeping C98 compatibility by default) and just integrate them to have the problem solved right now. If it works then there is no drawback that I can see. And the first implementation can easily be replaced later with a better one when Phil has time to devote to it. If you (Phil) want a "better" first implementation using your own ideas, then let me repeat my earlier offer to help, if you would describe how you would like this to be done. |
If one were to do a proper thread safe CATCH, you'd keep per-thread lock free results which are only collated and sorted into the correct order at the test end. Right now my hacked threadsafe CATCH is pretty useless for multithreaded testing because the mutex I'm using synchronises all the threads which ruins the test. I'm working around it with this idiom: if(!(test)) ... which isn't ideal, but it solves my immediate problem. |
Why is it necessary to aggregate per thread first? Could one not simply run the test and pass the result to a single shared RunContext object (as it is now), but simply protect that last result-storing operation with a mutex? Holding a mutex for storing the result should not be a big problem, performance-wise. Naively it looks to me that one could probably remove the mutexes around the checks and instead just make the RunContext class thread-safe. I say naively because I am certain I am missing something vital here - or can it be that simple? |
To clarify: by "making RunContext thread safe" I mean not just adding a few mutex locks. One would also have to convert some of the "lastXYZ" member variables to thread local ones. But I think it would be pretty straight-forward. |
Multithreaded test cases are ruined by extra synchronisation. By per-thread lockfree results I specifically meant thread local storage. Note thread local storage (as in the C++11 thread_local) is profoundly broken on clang on OS X and FreeBSD right now unfortunately. |
The compiler/stdlib feature for thread-local is not supported by Apple's clang, that is correct. But pthread_key_create will work on Mac and BSD. This could also be a fallback for cases when C98 compatibility is needed. So I think one should:
So all in all one would have code that works on all C++11 compliant build systems and for C98 systems this would work for pthread-Unixes and Windows. I think that covers everything pretty well. Only a few lines of code would be needed for each of the three implementations, so I think this is not a big addition. So, assuming that we have working thread local storage: what else would be required, other than making RunContext safe? After reviewing the code I think that we do not need the reporters to be thread-safe, since when they are called before tests start and after they end this is all from the main thread and no test cases (i.e. other threads) are running at the time. During tests they are only called through the RunContext, which we can modify so that it mutexes the accesses. |
Another option would be to not actually do a "real" thread local storage implementation for the different platforms. That does seem a little out of place for a testing suite, I have to admit. Instead one could have a map or hash table in RunContext, mapping thread IDs to the thread state. This is very simple to implement and would only require a single platform dependent call for obtaining the thread id. It also makes cleaning up (and possibly analyzing) the thread states at the end of the test much easier. Performance-wise, in a single threaded test, looking up the value from a single entry map is super fast and should not matter - it is barely more than a pointer dereferencing. Even with hundreds of threads in a single test, the overhead should not be a big problem. And after each test one could clean up the "thread-local" states, so it cannot degenerate with huge test suites. I think I have convinced myself that this is a better solution for Catch. It reduces outside dependencies and platform-dependent code to a minimum. It would also make it pretty easy for a Catch user to provide his own getThreadID function if he is on an exotic platform that is not yet supported by the library. |
@ned14 could you elaborate on the "[tls] is profoundly broken on clang on OS X and FreeBSD right now"? |
@philsquared I was referring exclusively to the C++ 11 thread_local storage attribute. The libc on OS X and BSD doesn't implement the on thread exit registration using C++ 11 semantics yet due to race problems during shared library unload. You can still use __thread of course to use C semantics. Or a dense hash map indexed by thread id. Fastest would be the latter with the bucket cached into a TLS for fast access. |
Thanks Niall. That's a shame. |
... of course if I finished my persistent hash-trie project I might be able to use that too ;-) |
..hash-tnie, eh? superficially combining orthogonal ideas, but I"m sure you have a clever spin on it. Care to share? I may let you in early on heap of mini-heaps, but I fear I digress... |
Persistent hash-trie? There have been repeated attempts to get one of those into Boost as a prelude to entering the C++ standard library. Everybody to date has failed to make much progress ... and this is stretched over seven years now or something ... could I tempt you into submitting that for Boost? |
Oh, C semantics TLS works fine on all compilers, including MSVC, and has done so for years. It's just the C++ destructors won't fire unless you do it by hand. thread_local storage does fire destructors for you, it's very handy - except on OS X and BSD to date. |
Hi, |
I have made a start on a local branch, but it's been on hold for a while (too many balls in the air at the moment). |
If you're anything like me Phil, that ACCU conference sucks up all your free time from about February onwards! :) |
Hi, |
I am going to update documentation to mention that this is not supported and is unlikely to become supported until Catch 2, and close this with a revisit tag. |
Looks like there's more conversation about this in #1043. |
Hey Phil,
do you have plans for supporting test-cases which internally spawn several threads to check correct behavior under multi-threaded conditions? This is not about executing several test cases in parallel.
At least the
Junit
reporter does not support that very well and crashes; theConsole
reporter doesn't seem to have that issue for the same tests (at the moment) but the absence of any sync'ing mechanisms let me worry about that this could change anytime soon...For me it was enough to create a
ThreadSafeJunitReporter
by deriving fromJunitReporter
and adding amutex
/lock
to the methodassertionEnded()
just like this:And using the command line option
-r junit-thread-safe
to get stable test runs again.BTW: could you change this impl:
into this one:
This would allow someone to replace an existing reporter with an own implementation. The
insert()
call above just inserts the givenfactory
iff there is no factory withname
registered yet. Theoperator[]
call will insert or updatem_factories
in any case.The text was updated successfully, but these errors were encountered: