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

co.paralleluniverse.strands.channels.Selector Maven / Gradle / Ivy

Go to download

The core library for Fibers on Java, compatible with Java 11-16. Forked from puniverse/quasar

There is a newer version: 10.0.6
Show newest version
 /*
 * Quasar: lightweight strands and actors for the JVM.
 * Copyright (c) 2013-2014, 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.
 */
package co.paralleluniverse.strands.channels;

import co.paralleluniverse.common.monitoring.FlightRecorder;
import co.paralleluniverse.common.monitoring.FlightRecorderMessage;
import co.paralleluniverse.common.util.Debug;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.Synchronization;
import co.paralleluniverse.strands.Timeout;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Attempts to perform at most one channel operation (send or receive) of a given set.
 *
 * @author pron
 */
public class Selector implements Synchronization {
    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes.
     *
     * @param priority If {@code true} and more than one operation can complete at the same time, the one that appears in the given list first will be the one performed.
     *                 If {@code false} the order of the operations is ignored.
     * @param actions  a list of actions, one of which will be performed.
     * @return the action that has completed successfully
     * @throws InterruptedException
     */
    public static  SelectAction select(boolean priority, SelectAction... actions) throws InterruptedException, SuspendExecution {
        return new Selector(priority, Arrays.asList(actions)).select();
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes, but no longer than the given timeout.
     *
     * @param priority If {@code true} and more than one operation can complete at the same time, the one that appears in the given list first will be the one performed.
     *                 If {@code false} the order of the operations is ignored.
     * @param timeout  the maximum duration to block waiting for an operation to complete.
     * @param unit     the time unit of the given timeout
     * @param actions  a list of actions, one of which will be performed.
     * @return the action that has completed successfully, or {@code null} if the timeout expired before an operation could complete.
     * @throws InterruptedException
     */
    public static  SelectAction select(boolean priority, long timeout, TimeUnit unit, SelectAction... actions) throws InterruptedException, SuspendExecution {
        return new Selector(priority, Arrays.asList(actions)).select(timeout, unit);
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes, but no longer than the given timeout.
     *
     * @param priority If {@code true} and more than one operation can complete at the same time, the one that appears in the given list first will be the one performed.
     *                 If {@code false} the order of the operations is ignored.
     * @param timeout  the method will not block for longer than the amount remaining in the {@link Timeout}
     * @param actions  a list of actions, one of which will be performed.
     * @return the action that has completed successfully, or {@code null} if the timeout expired before an operation could complete.
     * @throws InterruptedException
     */
    public static  SelectAction select(boolean priority, Timeout timeout, SelectAction... actions) throws InterruptedException, SuspendExecution {
        return new Selector(priority, Arrays.asList(actions)).select(timeout);
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes.
     *
     * @param priority If {@code true} and more than one operation can complete at the same time, the one that appears in the given list first will be the one performed.
     *                 If {@code false} the order of the operations is ignored.
     * @param actions  a list of actions, one of which will be performed.
     * @return the action that has completed successfully
     * @throws InterruptedException
     */
    public static  SelectAction select(boolean priority, List> actions) throws InterruptedException, SuspendExecution {
        return new Selector(priority, actions instanceof ArrayList ? actions : new ArrayList<>(actions)).select();
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes, but no longer than the specified timeout.
     *
     * @param timeout  the maximum duration to block waiting for an operation to complete.
     * @param unit     the time unit of the given timeout
     * @param priority If {@code true} and more than one operation can complete at the same time, the one that appears in the given list first will be the one performed.
     *                 If {@code false} the order of the operations is ignored.
     * @param actions  a list of actions, one of which will be performed.
     * @return the action that has completed successfully
     * @throws InterruptedException
     */
    public static  SelectAction select(boolean priority, long timeout, TimeUnit unit, List> actions) throws InterruptedException, SuspendExecution {
        return new Selector(priority, actions instanceof ArrayList ? actions : new ArrayList<>(actions)).select(timeout, unit);
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes, but no longer than the specified timeout.
     *
     * @param timeout  the method will not block for longer than the amount remaining in the {@link Timeout}
     * @param priority If {@code true} and more than one operation can complete at the same time, the one that appears in the given list first will be the one performed.
     *                 If {@code false} the order of the operations is ignored.
     * @param actions  a list of actions, one of which will be performed.
     * @return the action that has completed successfully
     * @throws InterruptedException
     */
    public static  SelectAction select(boolean priority, Timeout timeout, List> actions) throws InterruptedException, SuspendExecution {
        return new Selector(priority, actions instanceof ArrayList ? actions : new ArrayList<>(actions)).select(timeout);
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes.
     * Same as calling {@link #select(boolean, co.paralleluniverse.strands.channels.SelectAction[]) select(false, actions)}.
     *
     * @param actions a list of actions, one of which will be performed.
     * @return the action that has completed successfully
     * @throws InterruptedException
     */
    public static  SelectAction select(SelectAction... actions) throws InterruptedException, SuspendExecution {
        return select(false, actions);
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes, but no longer than the given timeout.
     * Same as calling {@link #select(long, java.util.concurrent.TimeUnit, co.paralleluniverse.strands.channels.SelectAction[]) select(false, timeout, unit, actions)}.
     *
     * @param timeout the maximum duration to block waiting for an operation to complete.
     * @param unit    the time unit of the given timeout
     * @param actions a list of actions, one of which will be performed.
     * @return the action that has completed successfully, or {@code null} if the timeout expired before an operation could complete.
     * @throws InterruptedException
     */
    public static  SelectAction select(long timeout, TimeUnit unit, SelectAction... actions) throws InterruptedException, SuspendExecution {
        return select(false, timeout, unit, actions);
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes, but no longer than the given timeout.
     * Same as calling {@link #select(long, java.util.concurrent.TimeUnit, co.paralleluniverse.strands.channels.SelectAction[]) select(false, timeout, unit, actions)}.
     *
     * @param timeout the method will not block for longer than the amount remaining in the {@link Timeout}
     * @param actions a list of actions, one of which will be performed.
     * @return the action that has completed successfully, or {@code null} if the timeout expired before an operation could complete.
     * @throws InterruptedException
     */
    public static  SelectAction select(Timeout timeout, SelectAction... actions) throws InterruptedException, SuspendExecution {
        return select(false, timeout, actions);
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes.
     * Same as calling {@link #select(boolean, java.util.List) select(false, actions)}.
     *
     * @param actions a list of actions, one of which will be performed.
     * @return the action that has completed successfully
     * @throws InterruptedException
     */
    public static  SelectAction select(List> actions) throws InterruptedException, SuspendExecution {
        return select(false, actions);
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes, but no longer than the given timeout.
     * Same as calling {@link #select(boolean, long, java.util.concurrent.TimeUnit, java.util.List) select(false, timeout, unit, actions)}.
     *
     * @param timeout the maximum duration to block waiting for an operation to complete.
     * @param unit    the time unit of the given timeout
     * @param actions a list of actions, one of which will be performed.
     * @return the action that has completed successfully, or {@code null} if the timeout expired before an operation could complete.
     * @throws InterruptedException
     */
    public static  SelectAction select(long timeout, TimeUnit unit, List> actions) throws InterruptedException, SuspendExecution {
        return select(false, timeout, unit, actions);
    }

    /**
     * Performs exactly one channel operation of a given set, blocking until any of the actions completes, but no longer than the given timeout.
     * Same as calling {@link #select(boolean, long, java.util.concurrent.TimeUnit, java.util.List) select(false, timeout, unit, actions)}.
     *
     * @param timeout the method will not block for longer than the amount remaining in the {@link Timeout}
     * @param actions a list of actions, one of which will be performed.
     * @return the action that has completed successfully, or {@code null} if the timeout expired before an operation could complete.
     * @throws InterruptedException
     */
    public static  SelectAction select(Timeout timeout, List> actions) throws InterruptedException, SuspendExecution {
        return select(false, timeout, actions);
    }

    /**
     * Attempts to performs exactly one channel operation of a given set if one can be completed without blocking.
     * This method never blocks.
     *
     * @param priority If {@code true} and more than one operation can complete at the same time, the one that appears in the given list first will be the one performed.
     *                 If {@code false} the order of the operations is ignored.
     * @param actions  a list of actions, one of which will be performed.
     * @return the action that has completed successfully
     */
    public static  SelectAction trySelect(boolean priority, SelectAction... actions) throws SuspendExecution {
        return new Selector(priority, Arrays.asList(actions)).trySelect();
    }

    /**
     * Attempts to performs exactly one channel operation of a given set if one can be completed without blocking.
     * This method only blocks if the listeners associated with the select actions block.
     *
     * @param priority If {@code true} and more than one operation can complete at the same time, the one that appears in the given list first will be the one performed.
     *                 If {@code false} the order of the operations is ignored.
     * @param actions  a list of actions, one of which will be performed.
     * @return the action that has completed successfully
     */
    public static  SelectAction trySelect(boolean priority, List> actions) throws SuspendExecution {
        return new Selector(priority, actions instanceof ArrayList ? actions : new ArrayList<>(actions)).trySelect();
    }

    /**
     * Attempts to performs exactly one channel operation of a given set if one can be completed without blocking.
     * This method only blocks if the listeners associated with the select actions block. Same as calling {@link #trySelect(boolean, co.paralleluniverse.strands.channels.SelectAction[]) trySelect(false, actions)}.
     *
     * @param actions a list of actions, one of which will be performed.
     * @return the action that has completed successfully
     */
    public static  SelectAction trySelect(SelectAction... actions) throws SuspendExecution {
        return trySelect(false, actions);
    }

    /**
     * Attempts to performs exactly one channel operation of a given set if one can be completed without blocking.
     * This method only blocks if the listeners associated with the select actions block.. Same as calling {@link #trySelect(boolean, java.util.List) trySelect(false, actions)}.
     *
     * @param actions a list of actions, one of which will be performed.
     * @return the action that has completed successfully
     */
    public static  SelectAction trySelect(List> actions) throws SuspendExecution {
        return trySelect(false, actions);
    }

    //////////////////////
    /**
     * Creates a {@link SelectAction} for a send operation
     *
     * @param 
     * @param ch        The channel to which the operation tries to send the message
     * @param message   the message to send.
     * @return a send {@link SelectAction} that can be selected by the selector.
     */
    public static  SelectAction send(SendPort ch, Message message) {
        return send(ch, message, null);
    }

    /**
     * Creates a {@link SelectAction} for a send operation
     *
     * @param 
     * @param ch        The channel to which the operation tries to send the message
     * @param message   the message to send.
     * @param listener  a {@link SelectSendListener} which will be triggered if this operation succeeds.
     * @return a send {@link SelectAction} that can be selected by the selector.
     */
    public static  SelectAction send(SendPort ch, Message message, SelectSendListener listener) {
        return new SelectActionImpl((SendPort) ch, message, listener);
    }

    /**
     * Creates a {@link SelectAction} for a receive operation
     *
     * @param 
     * @param ch        the channel from which the operation tries to receive
     * @return a receive {@link SelectAction} that can be selected by the selector.
     */
    public static  SelectAction receive(ReceivePort ch) {
        return receive(ch, null);
    }

    /**
     * Creates a {@link SelectAction} for a receive operation
     *
     * @param 
     * @param ch        the channel from which the operation tries to receive
     * @param listener  a {@link SelectReceiveListener} which will be triggered if this operation succeeds.
     * @return a receive {@link SelectAction} that can be selected by the selector.
     */
    public static  SelectAction receive(ReceivePort ch, SelectReceiveListener listener) {
        return new SelectActionImpl(ch, listener);
    }
    ///////////////////
    private static final AtomicLong selectorId = new AtomicLong(); // used to break symmetry to prevent deadlock in transfer channel
    private static final Object LEASED = new Object() {
        @Override
        public String toString() {
            return "LEASED";
        }
    };
    final long id;
    private volatile Object winner;
    private Strand waiter;
    private final List> actions;
    private int lastRegistered;
    private final boolean priority;
    SelectAction res;

    Selector(boolean priority, List> actions) {
        this.id = selectorId.incrementAndGet();
        this.waiter = Strand.currentStrand();
        this.actions = (List>) actions;
        this.priority = priority;
        for (int i = 0; i < actions.size(); i++) {
            SelectActionImpl sa = (SelectActionImpl) actions.get(i);
            sa.setSelector(this);
            sa.setIndex(i);
            record("", "%s added %s", this, sa);
        }
    }

    private void selectInit() {
        if (!priority)
            Collections.shuffle(actions, ThreadLocalRandom.current());
    }

    void reset() {
        waiter = null;
        for (SelectActionImpl sa : actions)
            sa.resetReceive();
        winner = null;
    }

    SelectAction select() throws InterruptedException, SuspendExecution {
        return select(-1, null);
    }

    @Override
    public Object register() {
        Strand s = Strand.currentStrand();
        if (waiter != null && !waiter.equals(s))
            throw new IllegalMonitorStateException("A strand is already registered");
        this.waiter = Strand.currentStrand();

        final int n = actions.size();
        res = null;

        // register
        lastRegistered = -1;
        for (int i = 0; i < n; i++) {
            SelectActionImpl sa = actions.get(i);

            sa.token = sa.port.register((SelectActionImpl) sa);
            lastRegistered = i;
            if (sa.isDone()) {
                assert winner == sa; // seen to have failed in co.paralleluniverse.strands.channels.GeneralSelectorTest > testFans1[5] 
                res = sa;
                break;
            } else {
                Object w = winner;
                if (w != null & w != LEASED)
                    break;
            }
        }
        return null;
    }

    @Override
    public void unregister(Object registrationToken) {
        for (int i = 0; i <= lastRegistered; i++) {
            SelectActionImpl sa = actions.get(i);
            sa.port.unregister(sa.token);
            sa.token = null; // for GC
        }
        this.waiter = null;
    }

    SelectAction select(Timeout timeout) throws InterruptedException, SuspendExecution {
        return select(timeout.nanosLeft(), TimeUnit.NANOSECONDS);
    }

    SelectAction select(long timeout, TimeUnit unit) throws InterruptedException, SuspendExecution {
        if (timeout == 0 && unit != null)
            return trySelect();

        selectInit();

        final boolean timed = (timeout > 0 && unit != null);
        long lastTime = timed ? System.nanoTime() : 0L;
        long nanos = timed ? unit.toNanos(timeout) : 0L;

        Object token = register();
        try {
            if (res == null) {
                tryloop:
                for (;;) {
                    if (timed && nanos <= 0)
                        break;

                    for (int i = 0; i <= lastRegistered; i++) {
                        SelectActionImpl sa = actions.get(i);

                        if (sa.port.tryNow(sa.token)) {
                            res = sa;
                            break tryloop;
                        }
                    }

                    if (timed) {
                        long now = System.nanoTime();
                        if ((nanos -= now - lastTime) > 0)
                            Strand.parkNanos(this, nanos);
                        lastTime = now;
                    } else
                        Strand.park(this);
                }
            }
        } finally {
            unregister(token);
        }
        if (res != null)
            ((SelectActionImpl) res).fire();
        return res;
    }

    public SelectAction trySelect() throws SuspendExecution {
        selectInit();
        for (int i = 0; i < actions.size(); i++) {
            SelectActionImpl sa = actions.get(i);

            if (sa.isData()) {
                if (((SendPort) sa.port).trySend(sa.message())) {
                    sa.fire();
                    return sa;
                }
            } else {
                Object m = ((ReceivePort) sa.port).tryReceive();
                if (m != null || ((ReceivePort) sa.port).isClosed()) {
                    sa.setItem(m);
                    sa.fire();
                    return sa;
                }
            }
        }
        return null;
    }
    // private volatile StackTraceElement st[];

    boolean lease() {
        record("lease", "trying lease %s", this);
        Object w;
        int i = 0;
        long start = 0;
        do {
            w = winner;
            if (w != null & w != LEASED)
                return false;

            if (i++ > (1 << 22)) {
                if (start == 0)
                    start = System.nanoTime();
                else if (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) > 10000)
                    throw new RuntimeException("Unable to obtain selector lease in 10 seconds: " + w);
                i = 0;
                Thread.yield();
            }
        } while (!casWinner(null, LEASED));
        // st = Thread.currentThread().getStackTrace();
        record("lease", "got lease %s", this);
        return true;
    }

    void setWinner(SelectAction action) {
        record("setWinner", "won %s: %s", this, action);
        assert winner == LEASED;
        // st = null;
        winner = action;
    }

    void returnLease() {
        record("returnLease", "returned lease %s", this);
        assert winner == LEASED;
        // st = null;
        winner = null;
    }

    Strand getWaiter() {
        return waiter;
    }

    void signal() {
        waiter.unpark(this);
    }

    public SelectAction getWinner() {
        return (SelectAction) winner;
    }

    private static int[] randomIntArray(int n) {
        final ThreadLocalRandom random = ThreadLocalRandom.current();
        final int[] a = new int[n];
        for (int i = 1; i < n; i++) {
            int x = random.nextInt(i);
            a[i] = a[x];
            a[x] = i;
        }
        return a;
    }

    @Override
    public String toString() {
        return Selector.class.getName() + '@' + Long.toHexString(id);
    }
    
    private static final VarHandle WINNER;
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            WINNER = l.findVarHandle(Selector.class, "winner", Object.class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    private boolean casWinner(Object expected, Object update) {
        return WINNER.compareAndSet(this, expected, update);
    }
    
    static final FlightRecorder RECORDER = Debug.isDebug() ? Debug.getGlobalFlightRecorder() : null;

    boolean isRecording() {
        return RECORDER != null;
    }

    static void record(String method, String format) {
        if (RECORDER != null)
            RECORDER.record(1, new FlightRecorderMessage("Selector", method, format, null));
    }

    static void record(String method, String format, Object arg1) {
        if (RECORDER != null)
            RECORDER.record(1, new FlightRecorderMessage("Selector", method, format, new Object[]{arg1}));
    }

    static void record(String method, String format, Object arg1, Object arg2) {
        if (RECORDER != null)
            RECORDER.record(1, new FlightRecorderMessage("Selector", method, format, new Object[]{arg1, arg2}));
    }

    static void record(String method, String format, Object arg1, Object arg2, Object arg3) {
        if (RECORDER != null)
            RECORDER.record(1, new FlightRecorderMessage("Selector", method, format, new Object[]{arg1, arg2, arg3}));
    }

    static void record(String method, String format, Object arg1, Object arg2, Object arg3, Object arg4) {
        if (RECORDER != null)
            RECORDER.record(1, new FlightRecorderMessage("Selector", method, format, new Object[]{arg1, arg2, arg3, arg4}));
    }

    static void record(String method, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
        if (RECORDER != null)
            RECORDER.record(1, new FlightRecorderMessage("Selector", method, format, new Object[]{arg1, arg2, arg3, arg4, arg5}));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy