/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Feb 24, 2009 */ package com.bigdata.concurrent; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; import org.apache.log4j.Logger; import com.bigdata.counters.CounterSet; import com.bigdata.service.DataService; import com.bigdata.testutil.ExperimentDriver; import com.bigdata.testutil.ExperimentDriver.Result; import com.bigdata.util.DaemonThreadFactory; import com.bigdata.util.NV; import com.bigdata.util.concurrent.ThreadPoolExecutorStatisticsTask; /** * Suite of stress tests of the concurrency control mechanisms (without the * database implementation) - See {@link NonBlockingLockManager}. * <p> * Goals: * <p> * 1. higher concurrency of unisolated operations on the ds/journal with group * commit. this only requires a "lock" per writable named index, e.g., an * operation will lock exactly one resource. show consistency of the data in a * suite of stress tests with varying #s of threads, tasks, and resources. Each * task will lock exactly one resource - the unisolated named index on which it * would write. DO THIS W/O the TxDAG first and get group commit debugged before * trying to work through the tx commit stuff, which requires the WAITS_FOR * graph support. * <p> * 2. transaction processing integrated with unisolated operations. since a * transaction MAY write on more than one index this requires a TxDAG and a * queue of resources waiting to get into the granted group (which will always * be a singleton since writes on unisolated named indices require exclusive * locks). show that concurrency control never deadlocks in a suite of stress * tests with varying #s of threads, tasks, resources, and resource locked per * task. Again, a resource is a unisolated named index. * * @todo verify more necessary outcomes of the different tests using assertions. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public abstract class AbstractStressTestNonBlockingLockManager extends TestCase { protected static final Logger log = Logger .getLogger(StressTestNonBlockingLockManagerWithPredeclaredLocks.class); protected static final boolean INFO = log.isInfoEnabled(); protected static final boolean DEBUG = log.isDebugEnabled(); /** * */ public AbstractStressTestNonBlockingLockManager() { } /** * @param arg0 */ public AbstractStressTestNonBlockingLockManager(String arg0) { super(arg0); } /** * Waits 10ms once it acquires its locks. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public static class Wait10ResourceTask<T> implements Callable<T> { public T call() throws Exception { // if (INFO) // log.info("Executing: "+this); synchronized (this) { try { if (DEBUG) log.debug("Waiting: "+this); wait(10/* milliseconds */); // if (INFO) // log.info("Done waiting: "+this); } catch (InterruptedException e) { throw new RuntimeException(e); } } // if (INFO) // log.info("Done: "+this); return null; } } /** * Dies once it acquires its locks by throwing {@link HorridTaskDeath}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ static class DeathResourceTask<T> implements Callable<T> { public T call() throws Exception { if(DEBUG) log.debug("Arrgh!"); throw new HorridTaskDeath(); } } /** * Test driver. * <p> * Note: A "resource" is a named index (partition), so set nresources based * on your expectations for the #of index partitions on a journal or * federation. * <p> * Note: The likelyhood of deadlock increases as (a) more locks are * requested per task; and (b) fewer resources are available to be locked. * When minLocks==maxLocks==nresources then tasks will be serialized since * each task requires all resources in order to proceed. * <p> * Note: At minLocks==maxLocks==1 this test can be used to explore the * behavior of tasks that lock only a single resource, eg., unisolated * operations on the {@link DataService}. */ public Result doComparisonTest(final Properties properties) throws Exception { // if 0L use Long.MAX_VALUE else convert seconds to nanoseconds. final long testTimeout = (TimeUnit.SECONDS.toNanos(Long .parseLong(properties.getProperty(TestOptions.TIMEOUT, TestOptions.DEFAULT_TIMEOUT))) == 0L) ? Long.MAX_VALUE : TimeUnit.SECONDS.toNanos(Long.parseLong(properties .getProperty(TestOptions.TIMEOUT, TestOptions.DEFAULT_TIMEOUT))); final int corePoolSize = Integer.parseInt(properties .getProperty(TestOptions.CORE_POOL_SIZE)); final int maxPoolSize = (properties .getProperty(TestOptions.MAX_POOL_SIZE) == null ? corePoolSize : Integer.parseInt(properties .getProperty(TestOptions.MAX_POOL_SIZE))); if (maxPoolSize < corePoolSize) { throw new IllegalArgumentException(TestOptions.MAX_POOL_SIZE + "=" + maxPoolSize + ", but " + TestOptions.CORE_POOL_SIZE + "=" + corePoolSize); } final boolean prestartCoreThreads = Boolean.parseBoolean(properties .getProperty(TestOptions.PRESTART_CORE_THREADS, TestOptions.DEFAULT_PRESTART_CORE_THREADS)); final boolean synchronousQueue = Boolean.parseBoolean(properties .getProperty(TestOptions.SYNCHRONOUS_QUEUE, TestOptions.DEFAULT_SYNCHRONOUS_QUEUE)); final boolean synchronousQueueFair = Boolean.parseBoolean(properties .getProperty(TestOptions.SYNCHRONOUS_QUEUE_FAIR, TestOptions.DEFAULT_SYNCHRONOUS_QUEUE_FAIR)); final int ntasks = Integer.parseInt(properties .getProperty(TestOptions.NTASKS)); final double percentTaskDeath = Double.parseDouble(properties .getProperty(TestOptions.PERCENT_TASK_DEATH, TestOptions.DEFAULT_PERCENT_TASK_DEATHS)); final int nresources = Integer.parseInt(properties .getProperty(TestOptions.NRESOURCES)); final int minLocks = Integer.parseInt(properties .getProperty(TestOptions.MIN_LOCKS)); final int maxLocks = Integer.parseInt(properties .getProperty(TestOptions.MAX_LOCKS)); // if 0L use Long.MAX_VALUE else convert milliseconds to nanoseconds. final long taskTimeout = (Long.parseLong(properties.getProperty( TestOptions.TASK_TIMEOUT, TestOptions.DEFAULT_TASK_TIMEOUT)) == 0L) ? Long.MAX_VALUE : TimeUnit.MILLISECONDS.toNanos(Long.parseLong(properties .getProperty(TestOptions.TASK_TIMEOUT, TestOptions.DEFAULT_TASK_TIMEOUT))); final int maxLockTries = Integer .parseInt(properties.getProperty(TestOptions.MAX_LOCK_TRIES, TestOptions.DEFAULT_MAX_LOCK_TRIES)); final boolean predeclareLocks = Boolean.parseBoolean(properties .getProperty(TestOptions.PREDECLARE_LOCKS, TestOptions.DEFAULT_PREDECLARE_LOCKS)); final boolean sortLockRequests = Boolean.parseBoolean(properties .getProperty(TestOptions.SORT_LOCK_REQUESTS, TestOptions.DEFAULT_SORT_LOCK_REQUESTS)); /* * Note: without pre-declaration of locks, you can expect high deadlock * rates when minLocks=maxLocks=nresources since all tasks will contend * for all resources and locks will be assigned incrementally. */ assert minLocks <= maxLocks; assert minLocks <= nresources; assert maxLocks <= nresources; assert maxLockTries >= 1; // used to execute the tasks. final ThreadPoolExecutor delegateService; if (synchronousQueue) { /* * zero capacity queue w/ (ideally) unbounded thread pool. * * Note: when maxPoolSize is less than [ntasks] then it is possible * for the delegate to be overrun which will result in rejected * execution exceptions. */ delegateService = new ThreadPoolExecutor( corePoolSize,// Integer.MAX_VALUE, //maxPoolSize,// Long.MAX_VALUE/* keepAliveTime */, TimeUnit.SECONDS/* keepAliveUnit */, new SynchronousQueue<Runnable>(synchronousQueueFair)); } else { /* * fixed size thread pool w/ an unbounded queue. * * @todo could optionally specify a bound for the queue to see the * effect of overrunning it, which would result in rejected * execution exceptions from the delegate. */ delegateService = new ThreadPoolExecutor( corePoolSize,// maxPoolSize,// Long.MAX_VALUE/* keepAliveTime */, TimeUnit.SECONDS/*keepAliveUnit*/, new LinkedBlockingQueue<Runnable>()); } if (prestartCoreThreads) { delegateService.prestartAllCoreThreads(); } final NonBlockingLockManagerWithNewDesign<String> lockManager = new NonBlockingLockManagerWithNewDesign<String>( maxPoolSize/* multi-programming level */, maxLockTries, predeclareLocks, sortLockRequests) { protected void ready(Runnable r) { delegateService.execute(r); } }; /* * Sample stuff at a once-per-second rate. */ final CounterSet delegateCounterSet = new CounterSet(); final ScheduledExecutorService sampleService; { final double w = ThreadPoolExecutorStatisticsTask.DEFAULT_WEIGHT; final long initialDelay = 0; // initial delay in ms. final long delay = 1000; // delay in ms. final TimeUnit unit = TimeUnit.MILLISECONDS; sampleService = Executors .newSingleThreadScheduledExecutor(new DaemonThreadFactory( getClass().getName() + ".sampleService")); // sample the delegate executor. final ThreadPoolExecutorStatisticsTask delegateQueueStatisticsTask = new ThreadPoolExecutorStatisticsTask( "delegateService", delegateService, null/* delegateServiceCounters */, w); delegateCounterSet.makePath("delegate").attach( delegateQueueStatisticsTask.getCounters()); sampleService.scheduleWithFixedDelay(delegateQueueStatisticsTask, initialDelay, delay, unit); // sample the lock service. sampleService.scheduleWithFixedDelay(lockManager.statisticsTask, initialDelay, delay, unit); } try { final Collection<LockCallableImpl<String, Object>> tasks = new ArrayList<LockCallableImpl<String, Object>>( ntasks); // distinct resource names. references are reused by reach task. final String[] resources = new String[nresources]; for (int i = 0; i < nresources; i++) { resources[i] = "resource" + i; } final Random r = new Random(); /* * Create tasks; each will use between minLocks and maxLocks * distinct resources. */ for (int i = 0; i < ntasks; i++) { // #of locks that this task will seek to acquire. final int nlocks = (minLocks == maxLocks ? minLocks : r .nextInt(maxLocks - minLocks) + minLocks); // final int nlocks = maxLocks; final String[] resource = new String[nlocks]; // find a [nlocks] unique resources to lock for this task. for (int j = 0; j < nlocks; j++) { int t; while (true) { // random resource index. t = r.nextInt(Integer.MAX_VALUE) % nresources; // ensure distinct resources for this task. boolean duplicate = false; for (int k = 0; k < j; k++) { if (resource[k] == resources[t]) { duplicate = true; break; } } if (!duplicate) break; } resource[j] = resources[t]; } /* * Create all tasks. They will be submitted below. */ final LockCallableImpl<String, Object> task; if (r.nextDouble() < percentTaskDeath) { task = new LockCallableImpl<String, Object>(resource, new DeathResourceTask<Object>()); } else { task = new LockCallableImpl<String, Object>(resource, new Wait10ResourceTask<Object>()); } tasks.add(task); } // submit the tasks for execution. final long begin = System.nanoTime(); final List<Future<Object>> futures = new LinkedList<Future<Object>>(); int nsubmitted = 0; { if (INFO) log.info("Submitting " + tasks.size() + " tasks: testTimeout=" + TimeUnit.NANOSECONDS.toSeconds(testTimeout)); long elapsed; { final Iterator<LockCallableImpl<String, Object>> itr = tasks .iterator(); while ((elapsed = (System.nanoTime() - begin)) < testTimeout && itr.hasNext()) { final LockCallableImpl<String, Object> task = itr .next(); futures.add(lockManager.submit( task.resource, task.task)); nsubmitted++; } } // normal shutdown (run to completion). if (INFO) { log.info("Submitting " + tasks.size() + " tasks in " + TimeUnit.NANOSECONDS.toMillis(elapsed) + "ms"); log.info("Shutting down the lock manager."); } lockManager.shutdown(); } // check the futures, blocking until each is available. int nerrors = 0; int ndeadlock = 0; int ntimeout = 0; int ncomplete = 0; int nsuccess = 0; int ncancel = 0; int nhorriddeath = 0; final Iterator<Future<Object>> itr = futures.iterator(); while (itr.hasNext()) { final Future<Object> future = itr.next(); while (true) { if (future.isCancelled()) { ncancel++; break; // future is done. } else { try { final long elapsed = System.nanoTime() - begin; if (elapsed > taskTimeout) { future.cancel(true/* mayInterruptIfRunning */); ntimeout++; // fall through. } try { future.get(1, TimeUnit.SECONDS); nsuccess++; break; } catch(CancellationException ex) { ncancel++; break; } catch (TimeoutException ex) { log.warn("Future not ready yet: task=" + future + ", service=" + lockManager); continue; // keep waiting. } } catch (ExecutionException ex) { if (ex.getCause() instanceof DeadlockException) { // fatal deadlock (exceeded the retry count). ndeadlock++; } else if (ex.getCause() instanceof HorridTaskDeath) { nhorriddeath++; } else { // some other error. nerrors++; log.error("Task threw: " + ex, ex); } break; // future is done. } // catch } } ncomplete++; // another future is done. } if (nerrors > 0) { log.warn("There were " + nerrors + " errors and " + ndeadlock + " deadlocks"); } // Done. System.out.println("\n-----------"+getName()+"-------------"); System.out.println(lockManager.toString()); System.out.println(delegateCounterSet); System.out.println(lockManager.getCounters().toString()); final Result result = new Result(); // elapsed ns from when we began to submit tasks. final long elapsed = System.nanoTime() - begin; // elapsed ms from when we began to submit tasks. final long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(elapsed); final double perSec = (ncomplete * 1000d) / elapsedMillis; // these are based on examination of the futures. result.put("nsubmitted", "" + nsubmitted); result.put("nsuccess", "" + nsuccess); result.put("ncomplete", "" + ncomplete); result.put("ncancel", "" + ncancel); result.put("ntimeout", "" + ntimeout); result.put("ndeadlock", "" + ndeadlock); result.put("nhorriddeath", "" + nhorriddeath); // Note: This is an expected error. // reporting from the lock manager. result.put("maxrunning", "" + lockManager.counters.maxRunning); // throughput metrics. result.put("perSec", "" + perSec); result.put("elapsed", "" + elapsedMillis); System.out.println(result.toString(true/* newline */)); return result; } finally { sampleService.shutdownNow(); lockManager.shutdownNow(); delegateService.shutdownNow(); // System.out.println("-------------------\n"); } } /** * Options for * {@link StressTestNonBlockingLockManagerWithPredeclaredLocks#doComparisonTest(Properties)}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public static class TestOptions { /** * Maximum amount of time that the test will run (seconds) or ZERO (0) * if there is no timeout. */ public static final String TIMEOUT = "testTimeout"; /** * Boolean option determines whether a {@link SynchronousQueue} or an * unbounded queue will be used for the work queue of the * {@link ThreadPoolExecutor} consuming the ready tasks (default is * {@value #DEFAULT_SYNCHRONOUS_QUEUE}). * <p> * Note: The {@link ThreadPoolExecutor} must not block when we submit a * {@link Runnable} to it from * {@link NonBlockingLockManagerWithNewDesign#ready(Runnable)}. This * goal can be achieved either by having a {@link SynchronousQueue} and * an unbounded {@link #MAX_POOL_SIZE} -or- by using an unbounded work * queue. In the former case new {@link Thread}s will be created on * demand. In the latter can tasks will be queued until the * {@link #CORE_POOL_SIZE} threads can process them. * * @see ThreadPoolExecutor */ public static final String SYNCHRONOUS_QUEUE = "synchronousQueue"; /** * When using a {@link SynchronousQueue} for the * {@link ThreadPoolExecutor}'s work queue, this boolean property * specifies whether or not the {@link SynchronousQueue} will be fair, * which is a ctor property for {@link SynchronousQueue}. */ public static final String SYNCHRONOUS_QUEUE_FAIR = "synchronousQueueFair"; /** * The core thread pool size. */ public static final String CORE_POOL_SIZE = "nthreads"; /** * The #of concurrent threads (multi-programming level). This must be * GTE to {@link #CORE_POOL_SIZE}. The default value is whatever was * specified for {@link #CORE_POOL_SIZE}. The value is ignored if you * specify {@link #SYNCHRONOUS_QUEUE} as <code>true</code> and an * unbounded thread pool is used instead. */ public static final String MAX_POOL_SIZE = "maxPoolThreads"; /** * When <code>true</code> the core thread pool will be prestarted when * the {@link ThreadPoolExecutor} is created. */ public static final String PRESTART_CORE_THREADS = "prestartCoreThreads"; /** * Total #of tasks to execute. */ public static final String NTASKS = "ntasks"; /** * The percentage of tasks that will die a {@link HorridTaskDeath} in * [0.0:1.0] (default is 0.0). This is used to stress the error handling * mechanisms. */ public static final String PERCENT_TASK_DEATH = "percentTaskDeath"; /** * The #of declared resources. */ public static final String NRESOURCES = "nresources"; /** * The minimum #of locks that a task will seek to acquire. */ public static final String MIN_LOCKS = "minLocks"; /** * The maximum #of locks that a task will seek to acquire. */ public static final String MAX_LOCKS = "maxLocks"; /** * The timeout for the task in (milliseconds) -or- <code>0</code> iff * no timeout will be used. */ public static final String TASK_TIMEOUT = "lockTimeout"; /** * The maximum #of times that a task will attempt to acquire its locks * before failing. Temporary failures may occur due to deadlock or * timeout during lock acquisition. Such failures may be retried. The * minimum value is ONE (1) since that means that we make only one * attempt to obtain the necessary locks for the task. */ public static final String MAX_LOCK_TRIES = "maxLockTries"; /** * When true, operations MUST pre-declare their locks (default true). * <p> * Note: The {@link NonBlockingLockManager} uses this information to * avoid deadlocks by the simple expediency of sorting the resources in * each lock request into a common order. With this option deadlocks are * NOT possible but all locks MUST be pre-declared by the operation * before it begins to execute. */ public static final String PREDECLARE_LOCKS = "predeclareLocks"; /** * When true, the resources in a lock request are sorted before the lock * requests are issued (default true). This option is ONLY turned off * for testing purposes. Since predeclaration plus sorting makes * deadlocks impossible, this option MAY be turned off in order to * exercise the deadlock detection logic in {@link TxDag} and the * handling of deadlocks when they are detected. */ public static final String SORT_LOCK_REQUESTS = "sortLockRequest"; /** * The default is no timeout for the test. */ public static final String DEFAULT_TIMEOUT = "0"; public static final String DEFAULT_SYNCHRONOUS_QUEUE = "false"; public static final String DEFAULT_SYNCHRONOUS_QUEUE_FAIR = "false"; public static final String DEFAULT_PRESTART_CORE_THREADS = "false"; /** * The default is 1 try for locks. */ public static final String DEFAULT_MAX_LOCK_TRIES = "1"; /** * The default is no timeout for tasks. */ public static final String DEFAULT_TASK_TIMEOUT = "0"; /** * By default we do not force any tasks to die. */ public static final String DEFAULT_PERCENT_TASK_DEATHS = "0.0"; /** * By default the operations will predeclare their locks. */ public static final String DEFAULT_PREDECLARE_LOCKS = "true"; /** * By default lock requests will be sorted. */ public static final String DEFAULT_SORT_LOCK_REQUESTS = "true"; } public void setUpComparisonTest(Properties properties) throws Exception { } public void tearDownComparisonTest() throws Exception { } /** * Generates an XML file that can be used to (re-)run the concurrency * control tests. The outputs are appended to a file so you can see how * performance and collected counters change from run to run. * * @see ExperimentDriver * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ * * @todo refactor the tests generated below to use apply() and run each * basic condition with and without lock timeout and with and without * predeclaration of locks (and without sorting when locks are NOT * predeclared so that we can exercise the deadlock detection stuff). * We could also run each condition at (2), (20), and (100) threads. */ static public class Generate extends ExperimentDriver { /** * Generates an XML file that can be run by {@link ExperimentDriver}. * * @param args */ public static void main(String[] args) throws Exception { // this is the test to be run. String className = TestLockManager.class.getName(); Map<String,String> defaultProperties = new HashMap<String,String>(); /* * Set defaults for each condition. */ defaultProperties.put(TestOptions.TIMEOUT,"30"); // secs. defaultProperties.put(TestOptions.CORE_POOL_SIZE,"20"); defaultProperties.put(TestOptions.NTASKS,"1000"); defaultProperties.put(TestOptions.NRESOURCES,"100"); defaultProperties.put(TestOptions.MIN_LOCKS,"1"); defaultProperties.put(TestOptions.MAX_LOCKS,"3"); defaultProperties.put(TestOptions.MAX_LOCK_TRIES,"1"); defaultProperties.put(TestOptions.PREDECLARE_LOCKS,"false"); defaultProperties.put(TestOptions.SORT_LOCK_REQUESTS,"false"); defaultProperties.put(TestOptions.TASK_TIMEOUT,"1000"); // ms List<Condition>conditions = new ArrayList<Condition>(); // low concurrency. conditions.add(getCondition( defaultProperties, new NV[] { new NV(TestOptions.CORE_POOL_SIZE, "2") })); // and with a non-zero lock timeout. conditions.add(getCondition(defaultProperties, new NV[] { new NV(TestOptions.CORE_POOL_SIZE, "2"), new NV(TestOptions.TASK_TIMEOUT, "1000") })); // default concurrency. conditions.add(getCondition( defaultProperties, new NV[] {})); // and with a non-zero lock timeout. conditions.add(getCondition(defaultProperties, new NV[] { new NV( TestOptions.TASK_TIMEOUT, "1000") })); // default concurrency with 10% task death. conditions.add(getCondition( defaultProperties, new NV[] { new NV(TestOptions.PERCENT_TASK_DEATH, ".10") })); // and with a non-zero lock timeout. conditions.add(getCondition( defaultProperties, new NV[] { new NV(TestOptions.PERCENT_TASK_DEATH, ".10"), new NV(TestOptions.TASK_TIMEOUT, "1000") })); // high concurrency. conditions.add(getCondition( defaultProperties, new NV[] { new NV(TestOptions.CORE_POOL_SIZE, "100") })); // and with a non-zero lock timeout conditions.add(getCondition(defaultProperties, new NV[] { new NV(TestOptions.CORE_POOL_SIZE, "100"), new NV(TestOptions.TASK_TIMEOUT, "1000") })); // high concurrency with 10% task death. conditions.add(getCondition(defaultProperties, new NV[] { new NV(TestOptions.CORE_POOL_SIZE, "100"), new NV(TestOptions.PERCENT_TASK_DEATH, ".10") })); // and with a non-zero lock timeout. conditions.add(getCondition(defaultProperties, new NV[] { new NV(TestOptions.CORE_POOL_SIZE, "100"), new NV(TestOptions.PERCENT_TASK_DEATH, ".10"), new NV(TestOptions.TASK_TIMEOUT, "1000") })); // force sequential execution by limiting // nresources==minlocks==maxlocks. conditions.add(getCondition( defaultProperties, new NV[] { new NV(TestOptions.NRESOURCES, "1"), new NV(TestOptions.MIN_LOCKS, "1"), new NV(TestOptions.MAX_LOCKS, "1") })); // and with a non-zero lock timeout conditions.add(getCondition( defaultProperties, new NV[] { new NV(TestOptions.NRESOURCES, "1"), new NV(TestOptions.MIN_LOCKS, "1"), new NV(TestOptions.MAX_LOCKS, "1"), new NV(TestOptions.TASK_TIMEOUT,"1000") })); // force sequential execution by limiting nresources==minlocks==maxlocks. conditions.add(getCondition( defaultProperties, new NV[] { new NV(TestOptions.NRESOURCES, "3"), new NV(TestOptions.MIN_LOCKS, "3"), new NV(TestOptions.MAX_LOCKS, "3") })); // and with a non-zero lock timeout conditions.add(getCondition( defaultProperties, new NV[] { new NV(TestOptions.NRESOURCES, "3"), new NV(TestOptions.MIN_LOCKS, "3"), new NV(TestOptions.MAX_LOCKS, "3"), new NV(TestOptions.TASK_TIMEOUT,"1000") })); Experiment exp = new Experiment(className,defaultProperties,conditions); // copy the output into a file and then you can run it later. System.err.println(exp.toXML()); } } }