Package co.paralleluniverse.strands.channels

Source Code of co.paralleluniverse.strands.channels.TransferChannel$Node

/*
* Quasar: lightweight strands and actors for the JVM.
* Copyright (C) 2013, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*   or (per the licensee's choosing)
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
/*
* Based on j.u.c.LinkedTransferQueue:
*/
/*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package co.paralleluniverse.strands.channels;

import co.paralleluniverse.concurrent.util.UtilUnsafe;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.Strand;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
*
* @author Doug Lea
* @author pron
*/
public class TransferChannel<Message> implements Channel<Message>, Selectable<Message> {
    private volatile boolean sendClosed;
    private boolean receiveClosed;
    private static final Object CHANNEL_CLOSED = new Object();
    private static final Object NO_MATCH = new Object();
    private static final Object LOST = new Object();

    public TransferChannel() {
    }

    @Override
    public void send(Message message) throws SuspendExecution, InterruptedException {
        if (message == null)
            throw new IllegalArgumentException("message is null");
        if (isSendClosed())
            return;
        if (xfer1(message, true, SYNC, 0) != null)
            throw new InterruptedException(); // failure possible only due to interrupt
    }

    @Override
    public boolean send(Message message, long timeout, TimeUnit unit) throws SuspendExecution, InterruptedException {
        if (message == null)
            throw new IllegalArgumentException("message is null");
        if (isSendClosed())
            return true;
        if (xfer1(message, true, TIMED, unit.toNanos(timeout)) == null)
            return true;
        if (!Strand.interrupted())
            return false;
        throw new InterruptedException();
    }

    @Override
    public boolean trySend(Message message) {
        if (message == null)
            throw new IllegalArgumentException("message is null");
        if (isSendClosed())
            return true;
        boolean res = (trySendOrReceive(message, true) == null);
        return res;
    }

    @Override
    public void close() {
        if (!sendClosed) {
            sendClosed = true;
            signalWaitersOnClose();
        }
    }

    private void setReceiveClosed() {
        if (!receiveClosed)
            this.receiveClosed = true;
    }

    @Override
    public Message tryReceive() {
        if (receiveClosed)
            return null;

        final Object m = trySendOrReceive(null, false);

        if (m == CHANNEL_CLOSED)
            return null;
        return (Message) m;
    }

    @Override
    public Object register(SelectAction<Message> action) {
        return xfer0(action);
    }

    @Override
    public boolean tryNow(Object token) {
        Token t = (Token) token;
        return t.n.isMatched();
    }

    @Override
    public void unregister(Object token) {
        Token t = (Token) token;
        if (token == null)
            return;
        Node p = t.n;
        Node pred = t.pred;
        Object x = p.item;
        if (!((x == p) || ((x == null) == p.isData))) {
            if (p.casItem(x, p))        // cancel
                unsplice(pred, p);
        }
    }

    @Override
    public Message receive() throws SuspendExecution, InterruptedException {
        if (receiveClosed)
            return null;

        Object m = xfer1(null, false, SYNC, 0);

        if (m != null) {
            if (m == CHANNEL_CLOSED)
                return null;
            return (Message) m;
        }
        Thread.interrupted();
        throw new InterruptedException();
    }

    @Override
    public Message receive(long timeout, TimeUnit unit) throws SuspendExecution, InterruptedException {
        if (receiveClosed)
            return null;

        Object m = xfer1(null, false, TIMED, unit.toNanos(timeout));
        if (m != null || !Strand.interrupted()) {
            if (m == CHANNEL_CLOSED)
                return null;
            return (Message) m;
        }
        throw new InterruptedException();
    }

    boolean isSendClosed() {
        return sendClosed;
    }

    @Override
    public boolean isClosed() {
        return receiveClosed;
    }

    private void signalWaitersOnClose() {
        for (Node p = head; p != null;) {
            if (!p.isMatched()) {
                if (!p.isData) {
                    if (p.casItem(null, CHANNEL_CLOSED)) // match waiting requesters with CHANNEL_CLOSED
                        Strand.unpark(p.waiter, this);   // ... and wake 'em up
                } else
                    p.tryMatchData();
            }
            Node n = p.next;
            if (n != p)
                p = n;
            else
                p = head;
        }
    }
    /////////////////////////////////////////
    private static final long serialVersionUID = -3223113410248163686L;
    /**
     * True if on multiprocessor
     */
    private static final boolean MP = Runtime.getRuntime().availableProcessors() > 1;
    /**
     * The number of times to spin (with randomly interspersed calls
     * to Strand.yield) on multiprocessor before blocking when a node
     * is apparently the first waiter in the queue. See above for
     * explanation. Must be a power of two. The value is empirically
     * derived -- it works pretty well across a variety of processors,
     * numbers of CPUs, and OSes.
     */
    private static final int FRONT_SPINS = 1 << 7;
    /**
     * The number of times to spin before blocking when a node is
     * preceded by another node that is apparently spinning. Also
     * serves as an increment to FRONT_SPINS on phase changes, and as
     * base average frequency for yielding during spins. Must be a
     * power of two.
     */
    private static final int CHAINED_SPINS = FRONT_SPINS >>> 1;
    /**
     * The maximum number of estimated removal failures (sweepVotes)
     * to tolerate before sweeping through the queue unlinking
     * cancelled nodes that were not unlinked upon initial
     * removal. See above for explanation. The value must be at least
     * two to avoid useless sweeps when removing trailing nodes.
     */
    static final int SWEEP_THRESHOLD = 32;

    /**
     * Queue nodes. Uses Object, not E, for items to allow forgetting
     * them after use. Relies heavily on Unsafe mechanics to minimize
     * unnecessary ordering constraints: Writes that are intrinsically
     * ordered wrt other accesses or CASes use simple relaxed forms.
     */
    static final class Node {
        final boolean isData;   // false if this is a request node
        volatile SelectAction sa;
        volatile Object item;   // initially non-null if isData; CASed to match
        volatile Node next;
        volatile Strand waiter; // null until waiting

        // CAS methods for fields
        final boolean casNext(Node cmp, Node val) {
            return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }

        final boolean casItem(Object cmp, Object val) {
            // assert cmp == null || cmp.getClass() != Node.class;
            return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
        }

        /**
         * Constructs a new node. Uses relaxed write because item can
         * only be seen after publication via casNext.
         */
        Node(SelectAction sa) {
            UNSAFE.putObject(this, itemOffset, sa.message()); // relaxed write
            UNSAFE.putObject(this, saOffset, sa); // relaxed write
            this.isData = sa.isData();
        }

        Node(Object item, boolean isData) {
            UNSAFE.putObject(this, itemOffset, item); // relaxed write
            this.isData = isData;
        }

        /**
         * Links node to itself to avoid garbage retention. Called
         * only after CASing head field, so uses relaxed write.
         */
        final void forgetNext() {
            UNSAFE.putObject(this, nextOffset, this);
        }

        /**
         * Sets item to self and waiter to null, to avoid garbage
         * retention after matching or cancelling. Uses relaxed writes
         * because order is already constrained in the only calling
         * contexts: item is forgotten only after volatile/atomic
         * mechanics that extract items. Similarly, clearing waiter
         * follows either CAS or return from park (if ever parked;
         * else we don't care).
         */
        final void forgetContents() {
            UNSAFE.putObject(this, itemOffset, this);
            UNSAFE.putObject(this, saOffset, null);
            UNSAFE.putObject(this, waiterOffset, null);
        }

        /**
         * Returns true if this node has been matched, including the
         * case of artificial matches due to cancellation.
         */
        final boolean isMatched() {
            Object x = item;
            return (x == this) || ((x == null) == isData);
        }

        /**
         * Returns true if this is an unmatched request node.
         */
        final boolean isUnmatchedRequest() {
            return !isData && item == null;
        }

        /**
         * Returns true if a node with the given mode cannot be
         * appended to this node because this node is unmatched and
         * has opposite data mode.
         */
        final boolean cannotPrecede(boolean haveData) {
            boolean d = isData;
            Object x;
            return d != haveData && (x = item) != this && (x != null) == d;
        }

        /**
         * Tries to artificially match a data node -- used by remove.
         */
        final boolean tryMatchData() {
            // assert isData;
            Object x = item;
            if (x != null && x != this && casItem(x, null)) {
                Strand.unpark(waiter, this);
                return true;
            }
            return false;
        }

        boolean lease() {
            if (sa == null)
                return true;
            return sa.lease();
        }

        void returnLease() {
            if (sa != null)
                sa.returnLease();
        }

        void won() {
            if (sa != null) {
                Object x = item;
                sa.setItem(x == CHANNEL_CLOSED ? null : x);
                sa.won();
            }
        }
        private static final long serialVersionUID = -3375979862319811754L;
        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long itemOffset;
        private static final long saOffset;
        private static final long nextOffset;
        private static final long waiterOffset;

        static {
            try {
                UNSAFE = UtilUnsafe.getUnsafe();
                Class k = Node.class;
                itemOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("item"));
                saOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("sa"));
                nextOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("next"));
                waiterOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("waiter"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

    static class Token {
        final Node n;
        final Node pred;

        public Token(Node n, Node pred) {
            this.n = n;
            this.pred = pred;
        }
    }
    /**
     * head of the queue; null until first enqueue
     */
    transient volatile Node head;
    /**
     * tail of the queue; null until first append
     */
    private transient volatile Node tail;
    /**
     * The number of apparent failures to unsplice removed nodes
     */
    private transient volatile int sweepVotes;

    // CAS methods for fields
    private boolean casTail(Node cmp, Node val) {
        return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);
    }

    private boolean casHead(Node cmp, Node val) {
        return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
    }

    private boolean casSweepVotes(int cmp, int val) {
        return UNSAFE.compareAndSwapInt(this, sweepVotesOffset, cmp, val);
    }

    /*
     * Possible values for "how" argument in xfer method.
     */
    private static final int NOW = 0; // for untimed poll, tryTransfer
    private static final int ASYNC = 1; // for offer, put, add
    private static final int SYNC = 2; // for transfer, take
    private static final int TIMED = 3; // for timed poll, tryTransfer

    @SuppressWarnings("unchecked")
    static <E> E cast(Object item) {
        // assert item == null || item.getClass() != Node.class;
        return (E) item;
    }

    private Object trySendOrReceive(Message e, boolean haveData) {
        if (haveData && (e == null))
            throw new NullPointerException();
        Object item = tryMatch(null, e, haveData);
        if (item != NO_MATCH)
            return item;
        return e;
    }

    /**
     * Implements all queuing methods. See above for explanation.
     *
     * @param e the item or null for take
     * @param haveData true if this is a put, else a take
     * @param how NOW, ASYNC, SYNC, or TIMED
     * @param nanos timeout in nanosecs, used only if mode is TIMED
     * @return an item if matched, else e
     * @throws NullPointerException if haveData mode but e is null
     */
    private Object xfer1(Message e, boolean haveData, int how, long nanos) throws SuspendExecution {
        assert how == SYNC || how == TIMED;
        if (haveData && (e == null))
            throw new NullPointerException();

        Node s = null;                        // the node to append, if needed

        retry:
        for (;;) {                            // restart on append race
            Object item = tryMatch(null, e, haveData);
            if (item != NO_MATCH)
                return item;

            if (s == null)
                s = new Node(e, haveData);
            Node pred = tryAppend(s, haveData);
            if (pred == null)
                continue retry;           // lost race vs opposite mode
           
            if(!haveData && sendClosed) {
                s.item = CHANNEL_CLOSED;
                unsplice(pred, s);
                setReceiveClosed();
                return CHANNEL_CLOSED;
            }

            return awaitMatch(s, pred, e, (how == TIMED), nanos);
        }
    }

    private Token xfer0(SelectAction<Message> e) {
        final boolean haveData = e.isData();
        Node s = null;                        // the node to append, if needed

        retry:
        for (;;) {                            // restart on append race
            if (!e.lease())
                return null;
            if (receiveClosed || (sendClosed && e.isData())) {
                e.setItem(null);
                e.won();
                return null;
            }

            Object item = tryMatch(e, e.message(), haveData);
            if (item == LOST)
                return null;

            if (item != NO_MATCH) {
                e.setItem(item == CHANNEL_CLOSED ? null : (Message) item);
                e.won();
                return null;
            }
            e.returnLease();

            if (s == null) {
                s = new Node(e);
                s.waiter = e.selector().getWaiter();
            }
            Node pred = tryAppend(s, haveData);
            if (pred == null)
                continue retry;           // lost race vs opposite mode

            return new Token(s, pred);
        }
    }

    private Object tryMatch(SelectAction sa, Message e, boolean haveData) {
        boolean closed = isSendClosed(); // must be read before trying to match so as not to miss puts

        for (Node h = head, p = h; p != null;) { // find & match first node
            boolean isData = p.isData;
            Object item = p.item;

            if (item != p && (item != null) == isData) { // unmatched
                if (isData == haveData) // can't match
                    break;

                // avoid deadlock by orderdering lease acquisition:
                // if p requires a lease and is of lower hashCode than sa, we return sa's lease, acquire p's, and then reacquire sa's.
                SelectAction sa2 = p.sa;
                boolean leasedp;
                if (sa != null && sa2 != null
                        && sa2.selector().id < sa.selector().id) {
                    sa.returnLease();
                    leasedp = sa2.lease();
                    if (!sa.lease()) {
                        if (leasedp)
                            sa2.returnLease();
                        return LOST;
                    }
                } else
                    leasedp = p.lease();

                if (leasedp) {
                    if (p.casItem(item, e)) { // match
                        p.won();
                        for (Node q = p; q != h;) {
                            Node n = q.next;  // update by 2 unless singleton
                            if (head == h && casHead(h, n == null ? q : n)) {
                                h.forgetNext();
                                break;
                            }                 // advance and retry
                            if ((h = head) == null
                                    || (q = h.next) == null || !q.isMatched())
                                break;        // unless slack < 2
                        }
                        Strand.unpark(p.waiter, this);
                        return item;
                    } else
                        p.returnLease();
                }
            }
            Node n = p.next;
            p = (p != n) ? n : (h = head); // Use head if p offlist
        }

        if (closed) {
            assert !haveData;
            setReceiveClosed();
            return CHANNEL_CLOSED;
        }
        return NO_MATCH;
    }

    /**
     * Tries to append node s as tail.
     *
     * @param s the node to append
     * @param haveData true if appending in data mode
     * @return null on failure due to losing race with append in
     * different mode, else s's predecessor, or s itself if no
     * predecessor
     */
    private Node tryAppend(Node s, boolean haveData) {
        for (Node t = tail, p = t;;) {        // move p to last node and append
            Node n, u;                        // temps for reads of next & tail
            if (p == null && (p = head) == null) {
                if (casHead(null, s))
                    return s;                 // initialize
            } else if (p.cannotPrecede(haveData))
                return null;                  // lost race vs opposite mode
            else if ((n = p.next) != null)    // not last; keep traversing
                p = p != t && t != (u = tail) ? (t = u) : // stale tail
                        (p != n) ? n : null;      // restart if off list
            else if (!p.casNext(null, s))
                p = p.next;                   // re-read on CAS failure
            else {
                if (p != t) {                 // update if slack now >= 2
                    while ((tail != t || !casTail(t, s))
                            && (t = tail) != null
                            && (s = t.next) != null && // advance and retry
                            (s = s.next) != null && s != t);
                }
                return p;
            }
        }
    }

    /**
     * Spins/yields/blocks until node s is matched or caller gives up.
     *
     * @param s the waiting node
     * @param pred the predecessor of s, or s itself if it has no
     * predecessor, or null if unknown (the null case does not occur
     * in any current calls but may in possible future extensions)
     * @param e the comparison value for checking match
     * @param timed if true, wait only until timeout elapses
     * @param nanos timeout in nanosecs, used only if timed is true
     * @return matched item, or e if unmatched on interrupt or timeout
     */
    private Message awaitMatch(Node s, Node pred, Message e, boolean timed, long nanos) throws SuspendExecution {
        long lastTime = timed ? System.nanoTime() : 0L;
        Strand w = Strand.currentStrand();
        int spins = (w.isFiber() ? 0 : -1); // no spins in fiber; otherwise, initialized after first item and cancel checks
        ThreadLocalRandom randomYields = null; // bound if needed
       
        if(spins == 0)
            s.waiter = w;
       
        for (;;) {
            Object item = s.item;

            if (item == CHANNEL_CLOSED)
                setReceiveClosed();

            if (item != e) {                  // matched
                // assert item != s;
                s.forgetContents();           // avoid garbage
                return this.<Message>cast(item);
            }
            if ((w.isInterrupted() || (timed && nanos <= 0))
                    && s.casItem(e, s)) {        // cancel
                unsplice(pred, s);
                return e;
            }

            if (spins < 0) {                  // establish spins at/near front
                if ((spins = spinsFor(pred, s.isData)) > 0)
                    randomYields = ThreadLocalRandom.current();
            } else if (spins > 0) {             // spin
                --spins;
                if (randomYields.nextInt(CHAINED_SPINS) == 0)
                    Strand.yield();           // occasionally yield
            } else if (s.waiter == null) {
                s.waiter = w;                 // request unpark then recheck
            } else if (timed) {
                long now = System.nanoTime();
                if ((nanos -= now - lastTime) > 0)
                    Strand.parkNanos(this, nanos);
                lastTime = now;
            } else {
                Strand.park(this);
            }
        }
    }

    /**
     * Returns spin/yield value for a node with given predecessor and
     * data mode. See above for explanation.
     */
    private static int spinsFor(Node pred, boolean haveData) {
        if (MP && pred != null) {
            if (pred.isData != haveData)      // phase change
                return FRONT_SPINS + CHAINED_SPINS;
            if (pred.isMatched())             // probably at front
                return FRONT_SPINS;
            if (pred.waiter == null)          // pred apparently spinning
                return CHAINED_SPINS;
        }
        return 0;
    }

    /* -------------- Traversal methods -------------- */
    /**
     * Returns the successor of p, or the head node if p.next has been
     * linked to self, which will only be true if traversing with a
     * stale pointer that is now off the list.
     */
    final Node succ(Node p) {
        Node next = p.next;
        return (p == next) ? head : next;
    }

    /**
     * Returns the first unmatched node of the given mode, or null if
     * none. Used by methods isEmpty, hasWaitingConsumer.
     */
    private Node firstOfMode(boolean isData) {
        for (Node p = head; p != null; p = succ(p)) {
            if (!p.isMatched())
                return (p.isData == isData) ? p : null;
        }
        return null;
    }

    /**
     * Traverses and counts unmatched nodes of the given mode.
     * Used by methods size and getWaitingConsumerCount.
     */
    private int countOfMode(boolean data) {
        int count = 0;
        for (Node p = head; p != null;) {
            if (!p.isMatched()) {
                if (p.isData != data)
                    return 0;
                if (++count == Integer.MAX_VALUE) // saturated
                    break;
            }
            Node n = p.next;
            if (n != p)
                p = n;
            else {
                count = 0;
                p = head;
            }
        }
        return count;
    }

    /* -------------- Removal methods -------------- */
    /**
     * Unsplices (now or later) the given deleted/cancelled node with
     * the given predecessor.
     *
     * @param pred a node that was at one time known to be the
     * predecessor of s, or null or s itself if s is/was at head
     * @param s the node to be unspliced
     */
    final void unsplice(Node pred, Node s) {
        s.forgetContents(); // forget unneeded fields
        /*
         * See above for rationale. Briefly: if pred still points to
         * s, try to unlink s.  If s cannot be unlinked, because it is
         * trailing node or pred might be unlinked, and neither pred
         * nor s are head or offlist, add to sweepVotes, and if enough
         * votes have accumulated, sweep.
         */
        if (pred != null && pred != s && pred.next == s) {
            Node n = s.next;
            if (n == null
                    || (n != s && pred.casNext(s, n) && pred.isMatched())) {
                for (;;) {               // check if at, or could be, head
                    Node h = head;
                    if (h == pred || h == s || h == null)
                        return;          // at head or list empty
                    if (!h.isMatched())
                        break;
                    Node hn = h.next;
                    if (hn == null)
                        return;          // now empty
                    if (hn != h && casHead(h, hn))
                        h.forgetNext()// advance head
                }
                if (pred.next != pred && s.next != s) { // recheck if offlist
                    for (;;) {           // sweep now if enough votes
                        int v = sweepVotes;
                        if (v < SWEEP_THRESHOLD) {
                            if (casSweepVotes(v, v + 1))
                                break;
                        } else if (casSweepVotes(v, 0)) {
                            sweep();
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * Unlinks matched (typically cancelled) nodes encountered in a
     * traversal from head.
     */
    private void sweep() {
        for (Node p = head, s, n; p != null && (s = p.next) != null;) {
            if (!s.isMatched())
                // Unmatched nodes are never self-linked
                p = s;
            else if ((n = s.next) == null) // trailing node is pinned
                break;
            else if (s == n)    // stale
                // No need to also check for p == s, since that implies s == n
                p = head;
            else
                p.casNext(s, n);
        }
    }

    /**
     * Returns {@code true} if this queue contains no elements.
     *
     * @return {@code true} if this queue contains no elements
     */
    boolean isEmpty() {
        for (Node p = head; p != null; p = succ(p)) {
            if (!p.isMatched())
                return !p.isData;
        }
        return true;
    }

    boolean hasWaitingConsumer() {
        return firstOfMode(false) != null;
    }

    int size() {
        return countOfMode(true);
    }

    public int getWaitingConsumerCount() {
        return countOfMode(false);
    }
    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long headOffset;
    private static final long tailOffset;
    private static final long sweepVotesOffset;

    static {
        try {
            UNSAFE = UtilUnsafe.getUnsafe();
            Class k = TransferChannel.class;
            headOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("head"));
            tailOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("tail"));
            sweepVotesOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("sweepVotes"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}
TOP

Related Classes of co.paralleluniverse.strands.channels.TransferChannel$Node

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.