/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.runtime.internal; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * <h1>Shared Memory and Atomics</h1><br> * <h2>The Atomics Object</h2> * <ul> * <li>Runtime semantics * </ul> */ public final class Futex { private static final class Entry { private final Condition condition; private final ByteBuffer buffer; private int index; Entry(Condition condition, ByteBuffer buffer, int index) { this.condition = condition; this.buffer = buffer; this.index = index; } boolean matches(ByteBuffer buffer, int index) { return this.buffer == buffer && this.index == index; } } // FIXME: spec issue - define fairness property for 'futex critical section'? private final ReentrantLock lock = new ReentrantLock(true); private final LinkedList<Futex.Entry> queue = new LinkedList<>(); /** * Result enumeration for {@link Futex#wait(ByteBuffer, int, int, long, TimeUnit)}. */ public enum State { OK, NotEqual, Timedout; } /** * Causes the current agent to wait until it is waken or the timeout elapses. * * @param buffer * the byte buffer * @param index * the byte buffer index * @param value * the expected byte buffer value * @param timeout * the optional timeout or {@code -1} * @param timeUnit * the time unit of the timeout parameter * @return the result value * @throws InterruptedException * if interrupted while waiting */ public Futex.State wait(ByteBuffer buffer, int index, int value, long timeout, TimeUnit timeUnit) throws InterruptedException { lock.lock(); try { int w = UnsafeHolder.getIntVolatile(buffer, index); if (w != value) { return State.NotEqual; } Futex.Entry entry = new Entry(lock.newCondition(), buffer, index); queue.add(entry); if (entry.condition.await(timeout, timeUnit)) { return State.OK; } queue.remove(entry); return State.Timedout; } finally { lock.unlock(); } } /** * Wakes up a number of currently waiting agents. * * @param buffer * the byte buffer * @param index * the byte buffer index * @param count * the maximum number of agents to wake * @return the actual number of agents awoken */ public int wake(ByteBuffer buffer, int index, int count) { lock.lock(); try { int n = 0; for (Iterator<Futex.Entry> it = queue.iterator(); count > 0 && it.hasNext();) { Futex.Entry entry = it.next(); if (entry.matches(buffer, index)) { it.remove(); entry.condition.signal(); count -= 1; n += 1; } } return n; } finally { lock.unlock(); } } /** * Wakes up or requeues a number of currently waiting agents. * * @param buffer * the byte buffer * @param index1 * the first byte buffer index * @param count * the maximum number of agents to wake * @param index2 * the second byte buffer index * @param value * the expected byte buffer value * @return the actual number of agents awoken or {@code -1} if the current value does not match the expected value */ public int wakeOrRequeue(ByteBuffer buffer, int index1, int count, int index2, int value) { lock.lock(); try { int w = UnsafeHolder.getIntVolatile(buffer, index1); if (w != value) { return -1; } int n = 0; ArrayList<Futex.Entry> newEntries = new ArrayList<>(); for (Iterator<Futex.Entry> it = queue.iterator(); it.hasNext();) { Futex.Entry entry = it.next(); if (entry.matches(buffer, index1)) { if (count > 0) { it.remove(); entry.condition.signal(); count -= 1; n += 1; } else if (index1 != index2) { it.remove(); entry.index = index2; newEntries.add(entry); } else { break; } } } if (!newEntries.isEmpty()) { queue.addAll(newEntries); } return n; } finally { lock.unlock(); } } }