package org.infinispan.test.fwk; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * Behaves more or less like a map of {@link java.util.concurrent.Semaphore}s. * * One thread will wait for an event via {@code await(...)} or {@code awaitStrict(...)}, and one or more * other threads will trigger the event via {@code trigger(...)} or {@code triggerForever(...)}. * * @author Dan Berindei * @since 5.3 */ public class CheckPoint { private static final Log log = LogFactory.getLog(CheckPoint.class); public static final int INFINITE = 999999999; private final Lock lock = new ReentrantLock(); private final Condition unblockCondition = lock.newCondition(); private final Map<String, EventStatus> events = new HashMap<String, EventStatus>(); private boolean released = false; public void awaitStrict(String event, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { awaitStrict(event, 1, timeout, unit); } public boolean await(String event, long timeout, TimeUnit unit) throws InterruptedException { return await(event, 1, timeout, unit); } public void awaitStrict(String event, int count, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { if (!await(event, count, timeout, unit)) { throw new TimeoutException("Timed out waiting for event " + event); } } public boolean await(String event, int count, long timeout, TimeUnit unit) throws InterruptedException { log.tracef("Waiting for event %s * %d", event, count); lock.lock(); try { EventStatus status = null; long waitNanos = unit.toNanos(timeout); while (waitNanos > 0) { if (released) { log.trace("Unblocked all events."); return true; } status = events.get(event); if (status == null) { status = new EventStatus(); events.put(event, status); } if (status.available >= count) { status.available -= count; break; } waitNanos = unblockCondition.awaitNanos(waitNanos); } if (waitNanos <= 0) { log.errorf("Timed out waiting for event %s * %d (available = %d, total = %d", event, count, status.available, status.total); // let the triggering thread know that we timed out status.available = -1; return false; } log.tracef("Received event %s * %d (available = %d, total = %d)", event, count, status.available, status.total); return true; } finally { lock.unlock(); } } public String peek(long timeout, TimeUnit unit, String... expectedEvents) throws InterruptedException { log.tracef("Waiting for any one of events %s", Arrays.toString(expectedEvents)); String found = null; lock.lock(); try { long waitNanos = unit.toNanos(timeout); while (waitNanos > 0) { if (released) { return null; } for (String event : expectedEvents) { EventStatus status = events.get(event); if (status != null && status.available >= 1) { found = event; break; } } if (found != null) break; waitNanos = unblockCondition.awaitNanos(waitNanos); } if (waitNanos <= 0) { log.tracef("Timed out waiting for events %s", Arrays.toString(expectedEvents)); return null; } EventStatus status = events.get(found); log.tracef("Received event %s (available = %d, total = %d)", found, status.available, status.total); return found; } finally { lock.unlock(); } } public void trigger(String event) { trigger(event, 1); } public void triggerForever(String event) { trigger(event, INFINITE); } public void trigger(String event, int count) { lock.lock(); try { EventStatus status = events.get(event); if (status == null) { status = new EventStatus(); events.put(event, status); } else if (status.available < 0) { throw new IllegalStateException("Thread already timed out waiting for event " + event); } // If triggerForever is called more than once, it will cause an overflow and the waiters will fail. status.available = count != INFINITE ? status.available + count : INFINITE; status.total = count != INFINITE ? status.total + count : INFINITE; log.tracef("Triggering event %s * %d (available = %d, total = %d)", event, count, status.available, status.total); unblockCondition.signalAll(); } finally { lock.unlock(); } } public void triggerAll() { lock.lock(); try { released = true; unblockCondition.signalAll(); } finally { lock.unlock(); } } @Override public String toString() { return "CheckPoint" + events; } private class EventStatus { int available; int total; @Override public String toString() { return "" + available + "/" + total; } } }