/* * #%L * Sequence.java - mongodb-async-driver - Allanbank Consulting, Inc. * %% * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc. * %% * 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. * #L% */ package com.allanbank.mongodb.client.connection.socket; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLongArray; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import com.allanbank.mongodb.LockType; import com.allanbank.mongodb.client.message.PendingMessageQueue; /** * Sequence provides the ability to synchronize the access to the socket's * output stream. The thread starts by reserving a position via the reserve * method and then prepares to send the messages. It will then wait for its turn * to send and finally release the sequence. * <p> * We use an array of longs to avoid false sharing. * </p> * * @api.no This class is <b>NOT</b> part of the drivers API. This class may be * mutated in incompatible ways between any two releases of the driver. * @copyright 2013, Allanbank Consulting, Inc., All Rights Reserved */ public class Sequence { /** The offset of the release value. */ private static final int RELEASE_OFFSET = 15 + 7; /** The offset of the reserve value. */ private static final int RESERVE_OFFSET = 7; /** Amount of time to spin/yield before waiting. Set to 1/2 millisecond. */ private static final long YIELD_TIME_NS = PendingMessageQueue.YIELD_TIME_NS; /** The condition used when there are waiters. */ private final Condition myCondition; /** The mutex used with the sequence. */ private final Lock myLock; /** The lock type to use with the sequence. */ private final LockType myLockType; /** The atomic array of long values. */ private final AtomicLongArray myPaddedValue = new AtomicLongArray(30); /** * The map of waiters and the condition they are waiting on to avoid the * thundering herd. Only access while holding the {@link #myLock lock}. */ private final SortedMap<Long, Condition> myWaiters; /** Tracks how many threads are waiting for a message or a space to open. */ private final AtomicInteger myWaiting; /** * Create a sequence with a specified initial value. * * @param initialValue * The initial value for this sequence. */ public Sequence(final long initialValue) { this(initialValue, LockType.MUTEX); } /** * Create a sequence with a specified initial value. * * @param initialValue * The initial value for this sequence. * @param lockType * The lock type to use with the sequence. */ public Sequence(final long initialValue, final LockType lockType) { myPaddedValue.set(RESERVE_OFFSET, initialValue); myPaddedValue.set(RELEASE_OFFSET, initialValue); myLockType = lockType; myLock = new ReentrantLock(true); myCondition = myLock.newCondition(); myWaiting = new AtomicInteger(0); myWaiters = new TreeMap<Long, Condition>(); } /** * Returns an estimate of the number of waiters. * * @return The waiters. */ public int getWaitersCount() { final long reserve = myPaddedValue.get(RESERVE_OFFSET); final long release = myPaddedValue.get(RELEASE_OFFSET); return (int) (release - reserve); } /** * Returns true if the sequence is idle (reserve == release). * * @return True if the sequence is idle. */ public boolean isIdle() { final long reserve = myPaddedValue.get(RESERVE_OFFSET); final long release = myPaddedValue.get(RELEASE_OFFSET); return (reserve == release); } /** * Checks if there is a waiter for the sequence to be released. * * @param expectedReserve * The expected value for the reserve if there is no waiter. * @return True if there is a waiter (e.g., the reserve has advanced). */ public boolean noWaiter(final long expectedReserve) { final long reserve = myPaddedValue.get(RESERVE_OFFSET); return (reserve == expectedReserve); } /** * Release the position in the sequence. * * @param expectedValue * The expected/reserved value for the sequence. * @param newValue * The new value for the sequence. */ public void release(final long expectedValue, final long newValue) { while (!compareAndSetRelease(expectedValue, newValue)) { // Let another thread make progress - should not really spin if we // did a waitFor. Thread.yield(); } notifyWaiters(); } /** * Reserves a spot in the sequence for the messages to be sent. * * @param numberOfMessages * The number of messages to be sent. * @return The current value of the sequence. */ public long reserve(final long numberOfMessages) { long current; long next; do { current = myPaddedValue.get(RESERVE_OFFSET); next = current + numberOfMessages; } while (!compareAndSetReserve(current, next)); return current; } /** * Waits for the reserved sequence to be released. * * @param wanted * The sequence to wait to be released. */ public void waitFor(final long wanted) { long releaseValue = myPaddedValue.get(RELEASE_OFFSET); while (releaseValue != wanted) { if (myLockType == LockType.LOW_LATENCY_SPIN) { long now = System.nanoTime(); final long yeildDeadline = now + YIELD_TIME_NS; releaseValue = myPaddedValue.get(RELEASE_OFFSET); while ((now < yeildDeadline) && (releaseValue != wanted)) { // Let another thread make progress. Thread.yield(); now = System.nanoTime(); releaseValue = myPaddedValue.get(RELEASE_OFFSET); } } // Block. if (releaseValue != wanted) { final Long key = Long.valueOf(wanted); Condition localCondition = myCondition; try { final int waitCount = myWaiting.incrementAndGet(); myLock.lock(); // Second tier try for FindBugs to see the unlock. try { // Check for more than 1 waiter. If so stand in line via // the waiters map. (This will wake threads in the order // they should be processed.) if (waitCount > 1) { localCondition = myLock.newCondition(); myWaiters.put(key, localCondition); } releaseValue = myPaddedValue.get(RELEASE_OFFSET); while (releaseValue != wanted) { localCondition.awaitUninterruptibly(); releaseValue = myPaddedValue.get(RELEASE_OFFSET); } } finally { if (localCondition != myCondition) { myWaiters.remove(key); } } } finally { myLock.unlock(); myWaiting.decrementAndGet(); } } } } /** * Perform a compare and set operation on the sequence release position. * * @param expectedValue * The expected current value. * @param newValue * The value to update to. * @return true if the operation succeeds, false otherwise. */ private boolean compareAndSetRelease(final long expectedValue, final long newValue) { return myPaddedValue.compareAndSet(RELEASE_OFFSET, expectedValue, newValue); } /** * Perform a compare and set operation on the sequence reserve position. * * @param expectedValue * The expected current value. * @param newValue * The value to update to. * @return true if the operation succeeds, false otherwise. */ private boolean compareAndSetReserve(final long expectedValue, final long newValue) { return myPaddedValue.compareAndSet(RESERVE_OFFSET, expectedValue, newValue); } /** * Notifies the waiting threads that the state of the sequence has changed. */ private void notifyWaiters() { if (myWaiting.get() > 0) { try { myLock.lock(); // Wake the reused condition. myCondition.signalAll(); // Wake up the condition with the lowest wanted. // No Thundering Herd! if (!myWaiters.isEmpty()) { myWaiters.get(myWaiters.firstKey()).signalAll(); } } finally { myLock.unlock(); } } } }