-Xjoinpoints:synchronization
flag to AspectJ. To do this in Eclipse, go to Preferences -> AspectJ Compiler -> Other -> Non-standard compiler options
and add -Xjoinpoints:synchronization
to the text field.
The instrumentation injects code into an existing Java program in order to control the thread scheduling in order to create repeatable scheduling decisions and circumvent any bias in the default scheduler. Deadlock and data race detection are performed at runtime by use of metadata to find cyclic locking dependencies and happens-before violations.
The concj.*
packages are excluded from instrumentation by default by the blacklist defined in BlacklistAspect
. Because of this, all test programs (JUnit tests) are stored in a different subpackage: concjtest
. Classes inside that package will have the appropriate instrumentation injected into them automatically, with several annotations modifying the exact way tests are run.
RunWith
annotation from JUnit can be used together with a special test runner that is capable of re-running the unit test multiple times in a row with possibly different thread scheduling decisions. Due to the deterministic thread scheduling caused by the instrumentation, re-running the same unit test some other way would just repeat the same scheduling decisions every time.
ConcJRunner
to set the desired number of times the unit test should be repeated.
ConcJRunner
, instrumentation is automatically started and stopped for each test but otherwise the main method of a program must be marked with this annotation in order to start the instrumentation.
Aside from the annotations, several other aspects of the instrumentation can also be modified by passing properties to the JVM (-Dpropertyname=propertyvalue
). These include the ability to tune the level of logging among other things. See Config
for further details.
AspectJ is responsible for injecting the required instrumentation into tested programs. Different parts of the instrumentation are divided between several different Aspects, all inheriting from BlacklistAspect
so that the packages which must or musn't be instrumented can be changed in a single place. The other aspects are:
synchronized
blocks. AspectJ requires the compiler option -Xjoinpoints:synchronization
to be capable of instrumenting these things (it should warn you if you forget).Although not technically required, most instrumented programs are expected to be written as unit tests. One problem with using JUnit for testing programs with potential concurrency issues is the way that framework handles exceptions in threads other than the main thread. The standard JUnit behavior is to completely ignore all exceptions other than those thrown from the main thread.
A more convenient behavior would be to count any uncaught exceptions in secondary threads as failing the test. One way to achieve this is to catch those exceptions and rethrow them from the main thread, which is the default behavior of the (user-overridable) exception handler in the thread manager. When reaching the end of the instrumented method, it checks if any uncaught exceptions have occurred in the program (including those thrown as a result of data races and deadlocks) and throws an appropriate exception if that's the case.
To perform tasks such as data race detection, the instrumentation needs to keep track of some additional metadata.
ObjectState
, keeps track of any reads/writes to an object's fields.ThreadState
that concerns itself with which monitors are acquired and released by a thread.AspectJ has limitations on which classes it can inject instrumentation into. Any classes belonging to the Java standard class library are outside the control of AspectJ and can't be changed. There are usually ways to work around this limitation, such as changing all calls to Thread.start()
instead of its implementation. Just remember that the internals of those classes are like a black box. Native code also can't be instrumented for obvious reasons.
Finalizers and garbage collection are another problem area. There's no guarantee when garbage collection will happen and finalizers may run at any time which defeats the otherwise deterministic thread scheduling. Because they don't really work with repeating testing, finalizers are disabled by default (they can be re-enabled by changing the config).