SEC-904: Moved multi-threaded tests into sandbox
This commit is contained in:
-213
@@ -1,213 +0,0 @@
|
||||
/* 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.springframework.security.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) {}
|
||||
}
|
||||
}
|
||||
-214
@@ -15,14 +15,10 @@
|
||||
|
||||
package org.springframework.security.context;
|
||||
|
||||
import junit.framework.ComparisonFailure;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link SecurityContextHolder}.
|
||||
*
|
||||
@@ -30,193 +26,13 @@ import java.util.Random;
|
||||
* @version $Id$
|
||||
*/
|
||||
public class SecurityContextHolderTests extends TestCase {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
|
||||
private static int errors = 0;
|
||||
|
||||
private static final int NUM_OPS = 5;
|
||||
private static final int NUM_THREADS = 5;
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
public SecurityContextHolderTests() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SecurityContextHolderTests(String arg0) {
|
||||
super(arg0);
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
private void loadStartAndWaitForThreads(boolean topLevelThread, String prefix, int createThreads,
|
||||
boolean expectAllThreadsToUseIdenticalAuthentication, boolean expectChildrenToShareAuthenticationWithParent) {
|
||||
Thread[] threads = new Thread[createThreads];
|
||||
errors = 0;
|
||||
|
||||
if (topLevelThread) {
|
||||
// PARENT (TOP-LEVEL) THREAD CREATION
|
||||
if (expectChildrenToShareAuthenticationWithParent) {
|
||||
// An InheritableThreadLocal
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
if ((i % 2) == 0) {
|
||||
// Don't inject auth into current thread; neither current thread or child will have authentication
|
||||
threads[i] = makeThread(prefix + "Unauth_Parent_" + i, true, false, false, true, null);
|
||||
} else {
|
||||
// Inject auth into current thread, but not child; current thread will have auth, child will also have auth
|
||||
threads[i] = makeThread(prefix + "Auth_Parent_" + i, true, true, false, true,
|
||||
prefix + "Auth_Parent_" + i);
|
||||
}
|
||||
}
|
||||
} else if (expectAllThreadsToUseIdenticalAuthentication) {
|
||||
// A global
|
||||
SecurityContextHolder.getContext()
|
||||
.setAuthentication(new UsernamePasswordAuthenticationToken("GLOBAL_USERNAME",
|
||||
"pass"));
|
||||
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
if ((i % 2) == 0) {
|
||||
// Don't inject auth into current thread;both current thread and child will have same authentication
|
||||
threads[i] = makeThread(prefix + "Unauth_Parent_" + i, true, false, true, true,
|
||||
"GLOBAL_USERNAME");
|
||||
} else {
|
||||
// Inject auth into current thread; current thread will have auth, child will also have auth
|
||||
threads[i] = makeThread(prefix + "Auth_Parent_" + i, true, true, true, true, "GLOBAL_USERNAME");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// A standard ThreadLocal
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
if ((i % 2) == 0) {
|
||||
// Don't inject auth into current thread; neither current thread or child will have authentication
|
||||
threads[i] = makeThread(prefix + "Unauth_Parent_" + i, true, false, false, false, null);
|
||||
} else {
|
||||
// Inject auth into current thread, but not child; current thread will have auth, child will not have auth
|
||||
threads[i] = makeThread(prefix + "Auth_Parent_" + i, true, true, false, false,
|
||||
prefix + "Auth_Parent_" + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// CHILD THREAD CREATION
|
||||
if (expectChildrenToShareAuthenticationWithParent || expectAllThreadsToUseIdenticalAuthentication) {
|
||||
// The children being created are all expected to have security (ie an InheritableThreadLocal/global AND auth was injected into parent)
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
String expectedUsername = prefix;
|
||||
|
||||
if (expectAllThreadsToUseIdenticalAuthentication) {
|
||||
expectedUsername = "GLOBAL_USERNAME";
|
||||
}
|
||||
|
||||
// Don't inject auth into current thread; the current thread will obtain auth from its parent
|
||||
// NB: As topLevelThread = true, no further child threads will be created
|
||||
threads[i] = makeThread(prefix + "->child->Inherited_Auth_Child_" + i, false, false,
|
||||
expectAllThreadsToUseIdenticalAuthentication, false, expectedUsername);
|
||||
}
|
||||
} else {
|
||||
// The children being created are NOT expected to have security (ie not an InheritableThreadLocal OR auth was not injected into parent)
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
// Don't inject auth into current thread; neither current thread or child will have authentication
|
||||
// NB: As topLevelThread = true, no further child threads will be created
|
||||
threads[i] = makeThread(prefix + "->child->Unauth_Child_" + i, false, false, false, false, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start and execute the threads
|
||||
startAndRun(threads);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
junit.textui.TestRunner.run(SecurityContextHolderTests.class);
|
||||
}
|
||||
|
||||
private Thread makeThread(final String threadIdentifier, final boolean topLevelThread,
|
||||
final boolean injectAuthIntoCurrentThread, final boolean expectAllThreadsToUseIdenticalAuthentication,
|
||||
final boolean expectChildrenToShareAuthenticationWithParent, final String expectedUsername) {
|
||||
final Random rnd = new Random();
|
||||
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
if (injectAuthIntoCurrentThread) {
|
||||
// Set authentication in this thread
|
||||
SecurityContextHolder.getContext()
|
||||
.setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||
expectedUsername, "pass"));
|
||||
|
||||
//System.out.println(threadIdentifier + " - set to " + SecurityContextHolder.getContext().getAuthentication());
|
||||
} else {
|
||||
//System.out.println(threadIdentifier + " - not set (currently " + SecurityContextHolder.getContext().getAuthentication() + ")");
|
||||
}
|
||||
|
||||
// Do some operations in current thread, checking authentication is as expected in the current thread (ie another thread doesn't change it)
|
||||
for (int i = 0; i < NUM_OPS; i++) {
|
||||
String currentUsername = (SecurityContextHolder.getContext().getAuthentication() == null)
|
||||
? null : SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
|
||||
if ((i % 7) == 0) {
|
||||
System.out.println(threadIdentifier + " at " + i + " username " + currentUsername);
|
||||
}
|
||||
|
||||
try {
|
||||
TestCase.assertEquals("Failed on iteration " + i + "; Authentication was '"
|
||||
+ currentUsername + "' but principal was expected to contain username '"
|
||||
+ expectedUsername + "'", expectedUsername, currentUsername);
|
||||
} catch (ComparisonFailure err) {
|
||||
errors++;
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(rnd.nextInt(250));
|
||||
} catch (InterruptedException ignore) {}
|
||||
}
|
||||
|
||||
// Load some children threads, checking the authentication is as expected in the children (ie another thread doesn't change it)
|
||||
if (topLevelThread) {
|
||||
// Make four children, but we don't want the children to have any more children (so anti-nature, huh?)
|
||||
if (injectAuthIntoCurrentThread && expectChildrenToShareAuthenticationWithParent) {
|
||||
loadStartAndWaitForThreads(false, threadIdentifier, 4,
|
||||
expectAllThreadsToUseIdenticalAuthentication, true);
|
||||
} else {
|
||||
loadStartAndWaitForThreads(false, threadIdentifier, 4,
|
||||
expectAllThreadsToUseIdenticalAuthentication, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, threadIdentifier);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public final void setUp() throws Exception {
|
||||
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
|
||||
}
|
||||
|
||||
private void startAndRun(Thread[] threads) {
|
||||
// Start them up
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
// Wait for them to finish
|
||||
while (stillRunning(threads)) {
|
||||
try {
|
||||
Thread.sleep(250);
|
||||
} catch (InterruptedException ignore) {}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean stillRunning(Thread[] threads) {
|
||||
for (int i = 0; i < threads.length; i++) {
|
||||
if (threads[i].isAlive()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void testContextHolderGetterSetterClearer() {
|
||||
SecurityContext sc = new SecurityContextImpl();
|
||||
sc.setAuthentication(new UsernamePasswordAuthenticationToken("Foobar", "pass"));
|
||||
@@ -240,34 +56,4 @@ public class SecurityContextHolderTests extends TestCase {
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void testSynchronizationCustomStrategyLoading() {
|
||||
SecurityContextHolder.setStrategyName(InheritableThreadLocalSecurityContextHolderStrategy.class.getName());
|
||||
assertTrue(new SecurityContextHolder().toString()
|
||||
.lastIndexOf("SecurityContextHolder[strategy='org.springframework.security.context.InheritableThreadLocalSecurityContextHolderStrategy'") != -1);
|
||||
loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, false, true);
|
||||
assertEquals("Thread errors detected; review log output for details", 0, errors);
|
||||
}
|
||||
|
||||
public void testSynchronizationGlobal() throws Exception {
|
||||
SecurityContextHolder.clearContext();
|
||||
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL);
|
||||
loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, true, false);
|
||||
assertEquals("Thread errors detected; review log output for details", 0, errors);
|
||||
}
|
||||
|
||||
public void testSynchronizationInheritableThreadLocal()
|
||||
throws Exception {
|
||||
SecurityContextHolder.clearContext();
|
||||
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
|
||||
loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, false, true);
|
||||
assertEquals("Thread errors detected; review log output for details", 0, errors);
|
||||
}
|
||||
|
||||
public void testSynchronizationThreadLocal() throws Exception {
|
||||
SecurityContextHolder.clearContext();
|
||||
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL);
|
||||
loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, false, false);
|
||||
assertEquals("Thread errors detected; review log output for details", 0, errors);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user