Setting up the software

  1. Install Eclipse (4.3)
    http://www.eclipse.org
  2. Install Eclipse AspectJ Development Tools (2.2.3) (uses AspectJ 1.7.3)
    http://www.eclipse.org/ajdt/
  3. Enable weaving of synchronized blocks by passing the -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.
  4. Several test programs are located in the concjtest package, run them with JUnit 4.

Design

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.

Instrumenting a test program

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.

Annotations

Configuration

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

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:

JUnit

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.

Metadata

To perform tasks such as data race detection, the instrumentation needs to keep track of some additional metadata.

Problems and Limitations

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).