All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.persistit.util.ThreadSequencer Maven / Gradle / Ivy

There is a newer version: 3.3.0
Show newest version
/**
 * Copyright © 2012 Akiban Technologies, Inc.  All rights reserved.
 * 
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v1.0 which
 * accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * This program may also be available under different license terms.
 * For more information, see www.akiban.com or contact [email protected].
 * 
 * Contributors:
 * Akiban Technologies, Inc.
 */

package com.persistit.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Semaphore;

/**
 * 

* Internal utility that allows tests to define execution sequences to confirm * specific concurrent execution patterns. Subject code incorporates calls to * the static method {@link #sequence(int)}. Each usage in the application code * should follow this pattern: *

*
    *
  • In the {@link SequencerConstants} interface, add a static variable with a * name denoting the place in code where sequencing will occur as follows: * *
     * 
     *   final static int SOME_LOCATION = ThreadSequence.allocate("SOME_LOCATION");
     * 
     * 
    * *
  • *
  • In the class being tested, add a static import to ThreadSequence.* and * then call itself: * *
     * 
     *      sequence(SOME_LOCATION);
     * 
     * 
    * *
  • *
*

* The {@link #allocate(String)} method simply allocates a unique integer and * stores an associated location name. Currently the maximum number of allocated * locations is 64. *

*

* The {@link #sequence(int)} method blocks on a Semaphore until a condition for * release is recognized. The condition is determined by a schedule that the * test class must define. *

*

* The entire ThreadSequence mechanism must be enabled via * {@link #enableSequencer(boolean)}. By default all calls to * {@link #sequence(int)} invoke an empty method in the NullSequencer subclass, * which is fast. *

*

* In conjunction with enabling the sequencer, a test must also add one or more * sequencing schedules. Each schedule denotes two sets: the "join set" and the * "release set". When a thread calls sequence(x), the thread adds location x to * a list of blocked locations. It then determines whether that set covers any * join set, and if so, it releases all threads in the associated release set. * For example, suppose there are three location A, B and C. Consider two * sections of code executed by two different threads:

 *   sequence(A)
 *   // do this before B
 *   sequence(C)
 * 
and
 *   sequence(B)
 *   // do this after A
 * 
A suitable schedule might include the * pairs (A, B)->(A) and (B, C)->(B, C). In this example one thread blocks until * both sequence(A) and sequence(B) have been called. Then the schedule (A, * B)->(A) releases the call to sequence(A) so that the first thread runs. When * the first thread calls sequence(C), the schedule (B,C)->(B,C) runs, releasing * the second thread's call to sequence(B) and allowing the first thread to * continue without blocking. *

*

* Follow existing usage in {@link SequencerConstants} for defining schedules. * Test classes should invoke the static method {@link #addSchedules(int[][])} * to add schedules defined in SequencerConstants. *

*

* Note: a malformed schedule can cause threads blocked within the sequence * method to remain blocked forever. *

* * @author peter * */ public class ThreadSequencer implements SequencerConstants { private final static DisabledSequencer DISABLED_SEQUENCER = new DisabledSequencer(); private final static EnabledSequencer ENABLED_SEQUENCER = new EnabledSequencer(); private static volatile Sequencer _sequencer = DISABLED_SEQUENCER; private final static List LOCATIONS = new ArrayList(); private final static List CONDITIONS = new ArrayList(); private final static int MAX_LOCATIONS = 64; public static class Condition { public boolean enabled() { return true; } } public synchronized static int allocate(final String locationName) { for (final String alreadyRegistered : LOCATIONS) { assert !alreadyRegistered.equals(locationName) : "Location name " + locationName + " is already in use"; } final int value = LOCATIONS.size(); assert value < MAX_LOCATIONS : "Too many ThreadSequence locations"; LOCATIONS.add(locationName); CONDITIONS.add(new Condition()); return value; } /** * Possibly block (only when ThreadSequencer is enabled for tests) until a * scheduling condition is met. This method is called from strategically * selected places in the main code to enable concurrency tests. See above * for details. * * @param location * integer uniquely identifying a location in code */ public static void sequence(final int location) { _sequencer.sequence(location); } /** * Enable sequencer debugging. * * @param history * indicates whether to maintain a schedule history */ public static void enableSequencer(final boolean history) { ENABLED_SEQUENCER.clear(); if (history) { ENABLED_SEQUENCER.enableHistory(); } _sequencer = ENABLED_SEQUENCER; } /** * Disable sequencer debugging. */ public static void disableSequencer() { _sequencer = DISABLED_SEQUENCER; ENABLED_SEQUENCER.clear(); } public static void addSchedule(final int[] awaitLocations, final int[] releaseLocations) { ENABLED_SEQUENCER.addSchedule(bits(awaitLocations), bits(releaseLocations)); } public static void addSchedules(final int[][] pairs) { for (int index = 0; index < pairs.length; index += 2) { addSchedule(pairs[index], pairs[index + 1]); } } public static void setCondition(final int location, final Condition condition) { CONDITIONS.set(location, condition); } public static String sequencerHistory() { return ENABLED_SEQUENCER.history(); } public static int[] rawSequenceHistoryCopy() { return ENABLED_SEQUENCER.rawHistoryCopy(); } public static void appendHistoryElement(final StringBuilder sb, int location) { if (location < MAX_LOCATIONS) { sb.append('+'); sb.append(LOCATIONS.get(location)); } else if ((location = out(location)) < MAX_LOCATIONS) { sb.append('-'); sb.append(LOCATIONS.get(location)); } } public static String describeHistory(final int[] history) { final StringBuilder sb = new StringBuilder(); if (history != null) { for (final Integer location : history) { if (sb.length() > 0) { sb.append(','); } appendHistoryElement(sb, location); } } return sb.toString(); } public static String describePartialOrdering(final int[]... args) { final StringBuilder builder = new StringBuilder(); for (int i = 0; i < args.length; ++i) { if (i != 0) { builder.append(','); } builder.append('{'); for (final int location : args[i]) { appendHistoryElement(builder, location); } builder.append('}'); } return builder.toString(); } /** * Compare a particular sequence history to a collection of required * subsets. That is, compare the total ordering of the history as specified * by the given ranges where the elements within a given range can be in any * order. *

* For example, a partialOrderings of: *

    *
  • * *
         * { { A_IN, B_IN }, { OUT_B }, { IN_C } }
         * 
    * *
  • *
* Could be satisfied by either of these history: *
    *
  • * *
         * { A_IN, B_IN, OUT_B, IN_C }
         * 
    * *
  • *
  • * *
         * { B_IN, A_IN, OUT_B, IN_C }
         * 
    * *
  • *
* But not by any that contains IN_C before OUT_B. *

*

* Note: Both parameters will be modified by sorting. *

* * @param history * An ordered history, e.g. from * {@link #rawSequenceHistoryCopy()}. * @param partialOrderings * Total ordering specification of unordered subsets * * @return true if the history fulfilled the required * orderings. */ public static boolean historyMeetsPartialOrdering(final int[] history, final int[]... partialOrderings) { /* * Sort each subset and equivalent ranges in the actual history. Then a * simple element wise comparison. */ int offset = 0; for (final int[] subset : partialOrderings) { Arrays.sort(subset); final int nextOffset = offset + subset.length; if (nextOffset > history.length) { return false; } Arrays.sort(history, offset, nextOffset); for (int i = 0; i < subset.length; ++i) { if (subset[i] != history[offset + i]) { return false; } } offset = nextOffset; } return true; } public static int[] array(final int... args) { return args; } public static int out(final int location) { return Integer.MAX_VALUE - location; } private static long bits(final int[] locations) { long bits = 0; for (final int location : locations) { assert location >= 0 && location < MAX_LOCATIONS : "Location must be between 0 and 63, inclusive"; bits |= (1L << location); } return bits; } interface Sequencer { /** * A location is a long with one bit set that denotes one of * MAX_LOCATIONS possible locations in code where a join point can * occur. * * @param location */ public void sequence(final int location); /** * Clear the schedule */ public void clear(); /** * Add an element to the schedule. The arguments each represent a set of * locations in code. A schedule element affects the behavior of the * {@link #sequence(int)} method. When a thread calls * sequence(location), that thread blocks until the set of all currently * blocked threads covers the await field of one of the schedule * elements. Once the await field is covered, then the threads waiting * at locations denoted by the release field of that schedule element * are allowed to continue. * * @param await * bit map of locations required to meet schedule * @param release * bit map of locations to release after schedule is met */ public void addSchedule(final long await, final long release); } private static class DisabledSequencer implements Sequencer { @Override public void sequence(final int location) { } @Override public void clear() { } @Override public void addSchedule(final long await, final long release) { } } private static class EnabledSequencer implements Sequencer { private final List _schedule = new ArrayList(); private final Semaphore[] _semaphores = new Semaphore[MAX_LOCATIONS]; private long _waiting = 0; private long _enabled = 0; private final int[] _waitingCount = new int[MAX_LOCATIONS]; private List _history; { for (int index = 0; index < _semaphores.length; index++) { _semaphores[index] = new Semaphore(0); } } @Override public void sequence(final int location) { assert location >= 0 && location < MAX_LOCATIONS : "Location must be between 0 and 63, inclusive"; Semaphore semaphore = null; if (!CONDITIONS.get(location).enabled()) { return; } synchronized (this) { if ((_enabled & (1L << location)) == 0) { return; } _waiting |= (1L << location); _waitingCount[location]++; semaphore = _semaphores[location]; long release = 0; for (int index = 0; index < _schedule.size(); index += 2) { final long await = _schedule.get(index); if ((_waiting & await) == await) { release = _schedule.get(index + 1); break; } } for (int index = 0; index < MAX_LOCATIONS; index++) { if ((release & (1L << index)) != 0) { if (location == index) { semaphore = null; } else { _semaphores[index].release(); } } } if (_history != null) { _history.add(location); } } if (semaphore != null) { try { semaphore.acquire(); } catch (final InterruptedException e) { throw new RuntimeException(e); } } synchronized (this) { if (_history != null) { _history.add(out(location)); } if (--_waitingCount[location] == 0) { _waiting &= ~(1L << location); } } } @Override public synchronized void clear() { _schedule.clear(); _waiting = 0; _enabled = 0; _history = null; for (int index = 0; index < _semaphores.length; index++) { _semaphores[index].release(1000); _semaphores[index] = new Semaphore(0); } } @Override public synchronized void addSchedule(final long await, final long release) { for (int index = 0; index < _schedule.size(); index += 2) { final long current = _schedule.get(index); assert (current & await) != current : "Schedules may not overlap"; assert (current & await) != await : "Schedules may not overlap"; assert (await & release) != 0 : "No thread is released"; } _schedule.add(await); _schedule.add(release); _enabled |= release; } private void enableHistory() { _history = new ArrayList(); } public synchronized String history() { String desc = ""; if (_history != null) { final int[] copy = rawHistoryCopy(); desc = describeHistory(copy); _history.clear(); } return desc; } public synchronized int[] rawHistoryCopy() { int[] historyCopy = null; if (_history != null) { historyCopy = new int[_history.size()]; for (int i = 0; i < _history.size(); ++i) { historyCopy[i] = _history.get(i); } } return historyCopy; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy