SEC-484: Multithreaded tests for SessionRegistryImpl.

This commit is contained in:
Luke Taylor 2007-08-30 19:26:24 +00:00
parent ad43d433b4
commit 2e8d16c538
1 changed files with 213 additions and 0 deletions

View File

@ -0,0 +1,213 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.acegisecurity.concurrent;
import junit.framework.TestCase;
import java.util.Set;
import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
/**
* Tests concurrency access to SessionRegistryImpl.
*
* @author Luke Taylor
* @version $Id$
*/
public class SessionRegistryImplMultithreadedTests extends TestCase {
private static final Random rnd = new Random();
private static boolean errorOccurred;
protected void setUp() throws Exception {
errorOccurred = false;
}
/**
* Reproduces the NPE mentioned in SEC-484 where a sessionId is removed from
* the set of sessions before it is removed from the list of sessions for a principal.
* getAllSessions(principal, false) then finds the sessionId in the principal's session list
* but reads null for the SessionInformation with the same Id.
* Note that this is not guaranteed to produce the error but is a good testing point. Increasing the number
* of sessions makes a failure more likely, but slows the test considerably.
* Inserting temporary sleep statements in SessionRegistryClassImpl will also help.
*/
public void testConcurrencyOfReadAndRemoveIsSafe() {
Object principal = "Joe Principal";
SessionRegistryImpl sessionregistry = new SessionRegistryImpl();
Set sessions = Collections.synchronizedSet(new HashSet());
// Register some sessions
for (int i = 0; i < 50; i++) {
String sessionId = Integer.toString(i);
sessions.add(sessionId);
sessionregistry.registerNewSession(sessionId, principal);
}
// Pile of readers to hammer the getAllSessions method.
for (int i=0; i < 10; i++) {
Thread reader = new Thread(new SessionRegistryReader(principal, sessionregistry));
reader.start();
}
Thread remover = new Thread(new SessionRemover("remover", sessionregistry, sessions));
remover.start();
while(remover.isAlive()) {
pause(250);
}
assertFalse("Thread errors detected; review log output for details", errorOccurred);
}
public void testConcurrentRemovalIsSafe() {
Object principal = "Some principal object";
SessionRegistryImpl sessionregistry = new SessionRegistryImpl();
// The session list (effectivelly the containers sessions).
Set sessions = Collections.synchronizedSet(new HashSet());
Thread registerer = new Thread(new SessionRegisterer(principal, sessionregistry, 100, sessions));
registerer.start();
int nRemovers = 4;
SessionRemover[] removers = new SessionRemover[nRemovers];
Thread[] removerThreads = new Thread[nRemovers];
for (int i = 0; i < removers.length; i++) {
removers[i] = new SessionRemover("remover" + i, sessionregistry, sessions);
removerThreads[i] = new Thread(removers[i], "remover" + i);
removerThreads[i].start();
}
while (stillRunning(removerThreads)) {
pause(500);
}
}
private boolean stillRunning(Thread[] threads) {
for (int i = 0; i < threads.length; i++) {
if (threads[i].isAlive()) {
return true;
}
}
return false;
}
private static class SessionRegisterer implements Runnable {
private SessionRegistry sessionregistry;
private int nIterations;
private Set sessionList;
private Object principal;
public SessionRegisterer(Object principal, SessionRegistry sessionregistry, int nIterations, Set sessionList) {
this.sessionregistry = sessionregistry;
this.nIterations = nIterations;
this.sessionList = sessionList;
this.principal = principal;
}
public void run() {
for (int i=0; i < nIterations && !errorOccurred; i++) {
String sessionId = Integer.toString(i);
sessionList.add(sessionId);
try {
sessionregistry.registerNewSession(sessionId,principal);
pause(20);
Thread.yield();
} catch(Exception e) {
e.printStackTrace();
errorOccurred = true;
}
}
}
}
private static class SessionRegistryReader implements Runnable {
private SessionRegistry sessionRegistry;
private Object principal;
public SessionRegistryReader(Object principal, SessionRegistry sessionregistry) {
this.sessionRegistry = sessionregistry;
this.principal = principal;
}
public void run() {
while (!errorOccurred) {
try {
sessionRegistry.getAllSessions(principal, false);
sessionRegistry.getAllPrincipals();
sessionRegistry.getAllSessions(principal, true);
Thread.yield();
} catch (Exception e) {
e.printStackTrace();
errorOccurred = true;
}
}
}
}
private static class SessionRemover implements Runnable {
private SessionRegistry sessionregistry;
private Set sessionList;
private String name;
public SessionRemover(String name, SessionRegistry sessionregistry, Set sessionList) {
this.name = name;
this.sessionregistry = sessionregistry;
this.sessionList = sessionList;
}
public void run() {
boolean finished = false;
while (!finished && !errorOccurred) {
if (sessionList.isEmpty()) {
finished = true;
// List of sessions appears to be empty but give it a chance to fill up again
System.out.println(name + ": Session list empty. Waiting.");
pause(500);
}
Object[] sessions = sessionList.toArray();
if (sessions.length > 0) {
finished = false;
String sessionId = (String) sessions[0];
System.out.println(name + ": removing " + sessionId);
try {
sessionregistry.removeSessionInformation(sessionId);
pause(rnd.nextInt(100));
sessionList.remove(sessionId);
Thread.yield();
} catch (Exception e) {
e.printStackTrace();
errorOccurred = true;
}
}
}
}
}
private static void pause(int length) {
try {
Thread.sleep(length);
} catch (InterruptedException ignore) {}
}
}