Automatic Deja vu tracing

In the previous blog posts on Deja vu we have seen how to add tracability in your code by applying the three constructs @Traced, @Impure, and @AttachThread.

For green field projects you can simply structure your code for this kind of traceability. But typically traceability is more relevant for legacy code. Code that has neither tests nor documentation, but just sits there and occasional spits out an exception.

What do you do about this code? You keep telling your manager that it must be re-written to make it more testable. And your manager tells you there is no money on the budget for touching this code. So what to do?

Auto-impure module

Following the pure and impure definitions of Deja vu, the Java API can easily be split into the two categories. Reading a file, fetching the system time, and getting a random number are all examples of impure. The Deja vu module auto-impure automatically wraps calls to impure operations. This means you don’t have to manually create method wrappers for this. Let’s first look at a standard Deja vu example for reading lines of a file:

private BufferedReader br;

@Traced
public void readFile( String filename ) {
   openFile( filename );
   String line;
   while ( null != ( line = readLine())) {
     // ...
   )
}

@Impure
private void openFile( String filename ) {
  br = new BufferedReader( new FileReader( filename ));
}

@Impure
private String readLine() {
  return br.readLine();
}

There are a few tricks to make it work. First of all the BufferedReader field is instantiated inside an impure method. In that way during replay this field is always null. Second of all, each line is read from another @Impure method again to make the replay work without ever touching the br instance directly. This makes the code structure unnatural.

Using the auto-impure module the code can be expressed naturally:

@Traced
public void readFile( String fileName ) throws IOException {
  BufferedReader br = new BufferedReader(new FileReader(fileName));

  String line;
  while ( null != (line = br.readLine())) {
    // ...
  }
}

We have no longer any need for impure methods, we simply go ahead and instantiate the Readers and read the lines. A trace situation will treat the call to br.readLine() as if it was annotated @Impure.

Replay is a little tricky. Remember that the trace should be able to replay in a sand boxed environment, i.e. the file we read in our production system is probably not available in the local development environment we replay it in. So the call to new FileReader(fileName) must not be done in replay. It is intercepted and instead a mock object is returned. Now the call to br.readLine() is treated as if marked impure therefore substituted with the next value in the trace captured from the original run.

AttachThread

Similarly for @AttachThread the auto impure module makes it possible to write the code without traditional Deja vu method constructs. The ExecutorService is behaving as if it was annotated with @AttachThread on each method accepting a runnable or callable to be executed in the thread pool. The following example shows traced threaded code.

@Traced
public void begin( Integer threads) {
  ExecutorService executorService = Executors.newCachedThreadPool();

  for ( int i=0; i<threads; i++ ) {
    executorService.submit(new Runner());
  }
}

class Runner implements Runnable {
  public void run() {
        try {
            Thread.sleep( (long) new Random().nextInt(1000) );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        UUID.randomUUID().toString();
    }
  }
}

To re-iterate the benefits of Deja vu for multi-threaded traces is that the replay has identical interleaving of impure parts as the original run.

Summary

The auto-impure module of the Deja vu project makes it possible to get traceability for your normal Java code, without the need for specific Deja vu method constructions of @Impure and @AttachThread. This is intended to be applicable of all the standard Java libraries and will make the barrier for using Deja vu much lower. The two major benefits of this is that: you don’t have to write code with an unnatural structure and tracing legacy code becomes possible.

Leave a Reply

Close Menu