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

kilim.Cell Maven / Gradle / Ivy

Go to download

Coroutines, continuations, fibers, actors and message passing for the JVM

There is a newer version: 2.0.2-jdk7
Show newest version
/* Copyright (c) 2006, Sriram Srinivasan
 *
 * You may distribute this software under the terms of the license 
 * specified in the file "License"
 */

package kilim;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import kilim.concurrent.VolatileReferenceCell;

/**
 * A cell is a single-space buffer that supports multiple producers and a single
 * consumer, functionally identical to Mailbox bounded to a size of 1 (and hence
 * optimized for this size)
 */

public class Cell implements PauseReason, EventPublisher {
    Queue                 srcs             = new ConcurrentLinkedQueue();
    public static final int                SPACE_AVAILABLE  = 1;
    public static final int                MSG_AVAILABLE    = 2;
    public static final int                TIMED_OUT        = 3;
    public static final Event              spaceAvailble = new Event(SPACE_AVAILABLE);
    public static final Event              messageAvailable = new Event(MSG_AVAILABLE);
    public static final Event              timedOut         = new Event(TIMED_OUT);
    // private static final String defaultName_ = "DEFAULT-CELL";

    VolatileReferenceCell sink             = new VolatileReferenceCell();
    VolatileReferenceCell               message          = new VolatileReferenceCell();

    // DEBUG stuff
    // To do: move into monitorable stat object
    /*
     * public int nPut = 0; public int nGet = 0; public int nWastedPuts = 0;
     * public int nWastedGets = 0;
     */
    public Cell() {
    }

    /**
     * Non-blocking, nonpausing get.
     * 
     * @param eo
     * . If non-null (and if there is no message), registers this observer. The
     * observer is notified with a MessageAvailable event when a put() is done.
     * 
     * @return buffered message if there's one, or null
     */
    public T get(EventSubscriber eo) {
        EventSubscriber producer = null;
        T ret;
        ret = message.get();
        if (ret == null) {
            addMsgAvailableListener(eo);
        } else {
            message.compareAndSet(ret, null);
            if (srcs.size() > 0) {
                producer = srcs.poll();
            }
        }
        if (producer != null) {
            producer.onEvent(this, spaceAvailble);
        }
        return ret;
    }

    /**
     * Non-blocking, nonpausing put.
     * 
     * @param eo
     * . If non-null, registers this observer and calls it with an
     * SpaceAvailable event when there's space.
     * @return buffered message if there's one, or null
     */
    public boolean put(T msg, EventSubscriber eo) {
        boolean ret = true; // assume we'll be able to enqueue
        EventSubscriber subscriber;
        if (msg == null)
            throw new NullPointerException("Null message supplied to put");

        if (message.compareAndSet(null, msg)) {
            subscriber = sink.get();
            // sink.set(null);
        } else {
            ret = false;
            subscriber = null;
            if (eo != null) {
                srcs.add(eo);
            }
        }
        // notify get's subscriber that something is available
        if (subscriber != null) {
            // removeMsgAvailableListener(subscriber);
            if (sink.compareAndSet(subscriber, null)) {
                subscriber.onEvent(this, messageAvailable);
            }
        }
        return ret;
    }

    /**
     * Get, don't pause or block.
     * 
     * @return stored message, or null if no message found.
     */
    public T getnb() {
        return get(null);
    }

    /**
     * @return non-null message.
     * @throws Pausable
     */
    public T get() throws Pausable {
        Task t = Task.getCurrentTask();
        T msg = get(t);
        while (msg == null) {
            Task.pause(this);
            removeMsgAvailableListener(t);
            msg = get(t);
        }
        return msg;
    }

    /**
     * @return non-null message.
     * @throws Pausable
     */
    public T get(long timeoutMillis) throws Pausable {
        final Task t = Task.getCurrentTask();
        T msg = get(t);
        long begin = System.currentTimeMillis();
        long time = timeoutMillis;
        while (msg == null) {
            t.timer.setTimer(time);
            t.scheduler.scheduleTimer(t.timer);
            Task.pause(this);
            t.timer.cancel();
            removeMsgAvailableListener(t);
            time = timeoutMillis - (System.currentTimeMillis() - begin);
            if (time <= 0) {
                break;
            }
            msg = get(t);
        }
        return msg;
    }

    public void addSpaceAvailableListener(EventSubscriber spcSub) {
        srcs.add(spcSub);
    }

    public void removeSpaceAvailableListener(EventSubscriber spcSub) {
        srcs.remove(spcSub);
    }

    public void addMsgAvailableListener(EventSubscriber msgSub) {
        if (sink.get() != null && sink.get() != msgSub) {
            throw new AssertionError("Error: A mailbox can not be shared by two consumers.  New = "
                    + msgSub + ", Old = " + sink);
        }
        sink.compareAndSet(null, msgSub);
    }

    public void removeMsgAvailableListener(EventSubscriber msgSub) {
        sink.compareAndSet(msgSub, null);
    }

    public boolean putnb(T msg) {
        return put(msg, null);
    }

    public void put(T msg) throws Pausable {
        Task t = Task.getCurrentTask();
        while (!put(msg, t)) {
            Task.pause(this);
            removeSpaceAvailableListener(t);
        }
    }

    public boolean put(T msg, int timeoutMillis) throws Pausable {
        final Task t = Task.getCurrentTask();
        long begin = System.currentTimeMillis();
        long time = timeoutMillis;
        while (!put(msg, t)) {
            t.timer.setTimer(time);
            t.scheduler.scheduleTimer(t.timer);
            Task.pause(this);
            t.timer.cancel();
            removeSpaceAvailableListener(t);
            time = timeoutMillis - (System.currentTimeMillis() - begin);
            if (time <= 0) {
                return false;
            }
        }
        return true;
    }

    public void putb(T msg) {
        putb(msg, 0 /* infinite wait */);
    }

    public class BlockingSubscriber implements EventSubscriber {
        public volatile boolean eventRcvd = false;
        public void onEvent(EventPublisher ep, Event e) {
            synchronized (Cell.this) {
                eventRcvd = true;
                Cell.this.notify();
            }
        }
        public void blockingWait(final long timeoutMillis) {
            long start = System.currentTimeMillis();
            long remaining = timeoutMillis;
            boolean infiniteWait = timeoutMillis == 0;
            synchronized (Cell.this) {
                while (!eventRcvd && (infiniteWait || remaining > 0)) {
                    try {
                        Cell.this.wait(infiniteWait ? 0 : remaining);
                    } catch (InterruptedException ie) {
                    }
                    long elapsed = System.currentTimeMillis() - start;
                    remaining -= elapsed;
                }
            }
        }
    }

    public void putb(T msg, final long timeoutMillis) {
        BlockingSubscriber evs = new BlockingSubscriber();
        if (!put(msg, evs)) {
            evs.blockingWait(timeoutMillis);
        }
        if (!evs.eventRcvd) {
            removeSpaceAvailableListener(evs);
        }
    }

    public boolean hasMessage() {
        return message.get() != null;
    }

    public boolean hasSpace() {
        return message.get() == null;
    }

    /**
     * retrieve a message, blocking the thread indefinitely. Note, this is a
     * heavyweight block, unlike #get() that pauses the Fiber but doesn't block
     * the thread.
     */

    public T getb() {
        return getb(0);
    }

    /**
     * retrieve a msg, and block the Java thread for the time given.
     * 
     * @param millis
     * . max wait time
     * @return null if timed out.
     */
    public T getb(final long timeoutMillis) {
        BlockingSubscriber evs = new BlockingSubscriber();
        T msg;

        if ((msg = get(evs)) == null) {
            evs.blockingWait(timeoutMillis);
            if (evs.eventRcvd) {
                msg = get(null); // non-blocking get.
                assert msg != null : "Received event, but message is null";
            }
        }
        if (msg == null) {
            removeMsgAvailableListener(evs);
        }
        return msg;
    }

    public String toString() {
        return "id:" + System.identityHashCode(this) + " " + message;
    }

    // Implementation of PauseReason
    public boolean isValid(Task t) {
        if (t == sink.get()) {
            return !hasMessage();
        } else if (srcs.contains(t)) {
            return !hasSpace();
        } else {
            return false;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy