Background
In a web project I'm working on just now, users can share and edit documents. Because of the potential for people overwriting other people's edits, or even deleting a document whilst someone else is working on it, we use a locking mechanism to ensure that only one person can edit the document at a time. The lock is acquired when someone starts editing, and is released when any of these conditions are true:- The user logs out.
- The user session expires.
- The user saves and closes the document.
Our project uses the Apache Shiro security library, a very versatile library that can be used in web and non-web projects, and has good support for testing. Up till now, though, all our tests ran in a single thread, with the result that only one user could be logged in at a time.
For our integration tests we needed to have:
- Several users logged on simultaneously, simulating concurrent web sessions.
- One user active at a time, whilst the other users wait.
- Active session management, so that session expiration and logout listeners would be triggered after session lifecycle events.
Solution
In this solution, we build on a mechanism discussed in a StackOverflow post about sequencing of threads. All the code discussed here is available on Github as a Maven project at https://github.com/otter606/shiro-multithread-junit. Just import into your IDE and runmvn test
to run.
Setting up Shiro.
Shiro provides a TestUtils class in its documentation. Our unit test class can extend this, or include it as a helper class. In our example, for ease of explanation, we'll extend from it.First of all, we'll just initialise a regular SecurityManager, using a configuration file, shiro.ini at the root of the classpath - this is a standard approach to initialising Shiro.
 @BeforeClass  
 public static void readShiroConfiguration() {  
           Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");  
           SecurityManager securityManager = factory.getInstance();  
           SecurityUtils.setSecurityManager(securityManager);  
           log.setLevel(Level.INFO);  
      }  
 The shiro.ini file is very simple, it just defines three users and their passwords:
 [users]  
 user1 = password1  
 user2 = password2  
 user3 = password3  
The code to login a user, and bind to a particular thread is boiler-plate code that we can put in a utility method.
 private Subject doLogin(User user) {  
      Subject subjectUnderTest = new Subject.Builder(SecurityUtils.getSecurityManager())  
                     .buildSubject();  
      subjectUnderTest.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));  
      setSubject(subjectUnderTest);  
      return subjectUnderTest;  
 }  
So, at the moment we just have some code to login a User - useful, but nothing new. Calling this method multiple times in the same thread is likely to lead to strange results, so now we need to set up a mechanism to run multiple threads in JUnit, where each thread performs actions for a particular user, in a sequenced manner, so that the other threads pause when one thread is active. In this manner, we can test a use case like:
- User1 logs in and accesses a resource R for editing.
- User2 logs in and and also requests to access R, but is rejected, as user 1 holds a lock,
- User1 closes resource R (but remains logged in).
- User2 now successfully accesses R.
- User1 logs out.
- User2 logs out.
Invokables and CountDown latches.
To run the test, we'll use a simple interface, Invokable, in which to define callback functions that perform a single step in the use case. public interface Invokable {  
   void invoke() throws Exception;  
 }  
And let's define an array of these - 6 long - to hold each action:
 Invokable[] invokables = new Invokable[6];  
 invokables[0] = new Invokable() {  
             // annotate these inner class methods with @Test  
             @Test  
     public void invoke() throws Exception {  
         log.info("logging in user1");  
         doLogin(u1);  
         log.info(" user1 doing some actions..");  
         log.info(" user1 pausing but still logged in.");  
             }  
         };  
 invokables[1] = new Invokable() {  
     public void invoke() throws Exception {  
         log.info("logging in user2");  
         doLogin(u2);  
         log.info(" user2 doing some actions..");  
         // some action  
         log.info(" user2 pausing but still logged in.");  
     }  
 };  
 //.. + 4 more Invokables defined for subsequent steps.
Now, we'll set up a mechanism to sequence the execution of these Invokables in different threads using a CountDown Latch mechanism. Here's how we'll call it:
 Map config = new TreeMap<>();  
 // these are the array indices of the Invokable [].  
 config.put("t1", new Integer[] { 0, 3 });  
 config.put("t2", new Integer[] { 1, 5, });  
 config.put("t3", new Integer[] { 2, 4 });  
 SequencedRunnableRunner runner = new SequencedRunnableRunner(config, invokables);  
 runner.runSequence();   In the code above, we define that we want to run 3 threads, and specify the indices of the Invokable [] that will run in each thread. I.e., we want Invokable[0] to run in thread t1, then invokable 1 to run in thread t2, etc.,
What happens under the hood in runSequence is as follows. We define an array of CountDownLatch objects. Each Invokable will wait on a CountDownLatch, and each latch will be counted down by the completion of its predecessor:
 public void runSequence() throws InterruptedException {  
 // Lock l = new ReentrantLock(true);  
 CountDownLatch[] conditions = new CountDownLatch[actions.length];  
 for (int i = 0; i < actions.length; i++) {  
 // each latch will be counted down by the action of its predecessor  
 conditions[i] = new CountDownLatch(1);  
 }  
 Thread[] threads = new Thread[nameToSequence.size()];  
 int i = 0;  
 for (String name : nameToSequence.keySet()) {  
    threads[i] = new Thread(new SequencedRunnable(name, conditions, actions,  
    nameToSequence.get(name)));  
    i++;  
 }  
 for (Thread t : threads) {  
     t.start();  
 }  
 try {  
 // tell the thread waiting for the first latch to wake up.  
     conditions[0].countDown();  
 } finally {  
   // l.unlock();  
 }  
 // wait for all threads to finish before leaving the test  
 for (Thread t : threads) {  
   t.join();  
 }  
 }  
In SequenceRunnable, we run an Invokable, and count down the next latch in the sequence:
 public void run() {  
   try {  
     for (int i = 0; i < sequence.length; i++) {  
       int toWaitForIndx = sequence[i];  
       try {  
         log.debug(name + ": waiting for event " + toWaitForIndx);  
         toWaitFor[toWaitForIndx].await();  
       } catch (InterruptedException e) {  
         e.printStackTrace();  
       }  
     log.debug(name + ": invoking action " + toWaitForIndx);  
     actions[toWaitForIndx].invoke();  
     if (toWaitForIndx < toWaitFor.length - 1) {  
       log.debug(name + "counting down for next latch " + (toWaitForIndx + 1));  
       toWaitFor[++toWaitForIndx].countDown();  
     } else  
       log.debug(name + " executed last invokable!");  
     }  
   } catch (Exception e) {  
     e.printStackTrace();  
   }  
 }  
That's it! Using this setup, we can login multiple users simultaneously in concurrent sessions, and perform actions for any user in a guaranteed order, thus being able to test thoroughly functionality that is affected by resource contention or locking.

