/* $Id$ */ /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.manifoldcf.core.lockmanager; import org.apache.manifoldcf.core.interfaces.*; import java.io.*; import java.util.*; import java.util.concurrent.atomic.*; import org.junit.*; import static org.junit.Assert.*; public class TestZooKeeperLocks extends ZooKeeperBase { protected File synchDir = null; protected final static int readerThreadCount = 10; protected final static int writerThreadCount = 5; @Test public void multiThreadZooKeeperLockTest() throws Exception { // First, set off the threads ZooKeeperConnectionPool pool = new ZooKeeperConnectionPool("localhost:8348",2000); LockObjectFactory factory = new ZooKeeperLockObjectFactory(pool); runTest(factory); } @Before public void createSynchDir() throws Exception { synchDir = new File("synchdir"); synchDir.mkdir(); } @After public void removeSynchDir() throws Exception { if (synchDir != null) deleteRecursively(synchDir); synchDir = null; } @Test public void multiThreadFileLockTest() throws Exception { runTest(new FileLockObjectFactory(synchDir)); } protected static void runTest(LockObjectFactory factory) throws Exception { String lockKey = "testkey"; AtomicInteger ai = new AtomicInteger(0); ReaderThread[] readerThreads = new ReaderThread[readerThreadCount]; for (int i = 0 ; i < readerThreadCount ; i++) { readerThreads[i] = new ReaderThread(factory, lockKey, ai); readerThreads[i].start(); } WriterThread[] writerThreads = new WriterThread[writerThreadCount]; for (int i = 0 ; i < writerThreadCount ; i++) { writerThreads[i] = new WriterThread(factory, lockKey, ai); writerThreads[i].start(); } for (int i = 0 ; i < readerThreadCount ; i++) { Throwable e = readerThreads[i].finishUp(); if (e != null) { if (e instanceof RuntimeException) throw (RuntimeException)e; if (e instanceof Error) throw (Error)e; if (e instanceof Exception) throw (Exception)e; } } for (int i = 0 ; i < writerThreadCount ; i++) { Throwable e = writerThreads[i].finishUp(); if (e != null) { if (e instanceof RuntimeException) throw (RuntimeException)e; if (e instanceof Error) throw (Error)e; if (e instanceof Exception) throw (Exception)e; } } } protected static void enterReadLock(Long threadID, LockGate lo) throws Exception { try { lo.enterReadLock(threadID); } catch (ExpiredObjectException e) { throw new ManifoldCFException("Unexpected exception: "+e.getMessage(),e); } } protected static void leaveReadLock(LockGate lo) throws Exception { try { lo.leaveReadLock(); } catch (ExpiredObjectException e) { throw new ManifoldCFException("Unexpected exception: "+e.getMessage(),e); } } protected static void enterWriteLock(Long threadID, LockGate lo) throws Exception { try { lo.enterWriteLock(threadID); } catch (ExpiredObjectException e) { throw new ManifoldCFException("Unexpected exception: "+e.getMessage(),e); } } protected static void leaveWriteLock(LockGate lo) throws Exception { try { lo.leaveWriteLock(); } catch (ExpiredObjectException e) { throw new ManifoldCFException("Unexpected exception: "+e.getMessage(),e); } } /** Reader thread */ protected static class ReaderThread extends Thread { protected final LockObjectFactory factory; protected final Object lockKey; protected final AtomicInteger ai; protected final Long threadID; protected Throwable exception = null; public ReaderThread(LockObjectFactory factory, Object lockKey, AtomicInteger ai) { setName("reader"); this.factory = factory; this.lockKey = lockKey; this.ai = ai; this.threadID = Thread.currentThread().getId(); } public void run() { try { // Create a new lock pool since that is the best way to insure real // zookeeper action. LockPool lp = new LockPool(factory); LockGate lo; // First test: count all reader threads inside read lock. // This guarantees that read locks are non-exclusive. // Enter read lock System.out.println("Entering read lock"); lo = lp.getObject(lockKey); enterReadLock(threadID,lo); try { System.out.println(" Read lock entered!"); // Count this thread ai.incrementAndGet(); // Wait until all readers have been counted. This test will hang if the readers function // exclusively while (ai.get() < readerThreadCount) { Thread.sleep(10L); } } finally { System.out.println("Leaving read lock"); leaveReadLock(lo); System.out.println(" Left read lock!"); } // Now, all the writers will get involved; we just need to make sure we never see an inconsistent value while (ai.get() < readerThreadCount + 2*writerThreadCount) { System.out.println("Waiting for all write threads to succeed..."); lo = lp.getObject(lockKey); enterReadLock(threadID,lo); try { // The writer thread will increment the counter twice for every thread, both times within the lock. // We never want to see the intermediate values. if ((ai.get() - readerThreadCount) % 2 == 1) throw new Exception("Was able to read when write lock in place"); } finally { leaveReadLock(lo); } Thread.sleep(100L); } System.out.println("Done with reader thread"); } catch (InterruptedException e) { } catch (Throwable e) { exception = e; } } public Throwable finishUp() throws InterruptedException { join(); return exception; } } /** Writer thread */ protected static class WriterThread extends Thread { protected final LockObjectFactory factory; protected final Object lockKey; protected final AtomicInteger ai; protected final Long threadID; protected Throwable exception = null; public WriterThread(LockObjectFactory factory, Object lockKey, AtomicInteger ai) { setName("writer"); this.factory = factory; this.lockKey = lockKey; this.ai = ai; this.threadID = Thread.currentThread().getId(); } public void run() { try { // Create a new lock pool since that is the best way to insure real // zookeeper action. // LockPool is a dummy LockPool lp = new LockPool(factory); LockGate lo; // The reader threads require ALL of the readers to get into the protected area. If // we try to write before that happens, ordering requirements produce a deadlock. So wait. while (ai.get() < readerThreadCount) { Thread.sleep(100L); } /* // Take write locks but free them immediately if read is what's active while (true) { lo = lp.getObject(lockKey); enterWriteLock(threadID,lo); try { System.out.println("Made it into read-time write lock"); // Check if we made it in during read cycle... that would be bad. if (ai.get() > 0 && ai.get() < readerThreadCount) throw new Exception("Was able to write even when readers were active"); if (ai.get() >= readerThreadCount) break; } finally { System.out.println("Leaving read-time write lock"); leaveWriteLock(lo); System.out.println("Left read-time write lock"); } Thread.sleep(1000L); } */ // Get write lock, increment twice, and leave write lock. Meanwhile, reader threads will be trying to gain access. lo = lp.getObject(lockKey); enterWriteLock(threadID,lo); try { if ((ai.get() - readerThreadCount) % 2 == 1) throw new Exception("More than one writer thread active at the same time!"); ai.incrementAndGet(); // Keep the lock for a while so other threads have to wait Thread.sleep(50L); // Increment again ai.incrementAndGet(); System.out.println("Updated write count"); } finally { leaveWriteLock(lo); } // Completed successfully! } catch (InterruptedException e) { } catch (Throwable e) { exception = e; } } public Throwable finishUp() throws InterruptedException { join(); return exception; } } }