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

kilim.Mailbox 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.LinkedList;

/**
 * This is a typed buffer that supports multiple producers and a single
 * consumer. It is the basic construct used for tasks to interact and
 * synchronize with each other (as opposed to direct java calls or static member
 * variables). put() and get() are the two essential functions.
 * 
 * We use the term "block" to mean thread block, and "pause" to mean
 * fiber pausing. The suffix "nb" on some methods (such as getnb())
 * stands for non-blocking. Both put() and get() have blocking and
 * non-blocking variants in the form of putb(), putnb
 */

public class Mailbox implements PauseReason, EventPublisher {
    // TODO. Give mbox a config name and id and make monitorable
    T[] msgs;
    private int iprod = 0; // producer index
    private int icons = 0; // consumer index;
    private int numMsgs = 0;
    private int maxMsgs = 300;
    EventSubscriber sink;
    
    // FIX: I don't like this event design. The only good thing is that
    // we don't create new event objects every time we signal a client
    // (subscriber) that's blocked on this mailbox.
    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);
    
    LinkedList srcs = new LinkedList();

    // 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 Mailbox() {
        this(10);
    }

    public Mailbox(int initialSize) {
        this(initialSize, Integer.MAX_VALUE);
    }

    @SuppressWarnings("unchecked")
    public Mailbox(int initialSize, int maxSize) {
        if (initialSize > maxSize)
            throw new IllegalArgumentException("initialSize: " + initialSize
                    + " cannot exceed maxSize: " + maxSize);
        msgs = (T[]) new Object[initialSize];
        maxMsgs = maxSize;
    }

    /**
     * Non-blocking, nonpausing get. 
     * @param eo. If non-null, registers this observer and calls it with a MessageAvailable event when
     *  a put() is done.
     * @return buffered message if there's one, or null 
     */
    public T get(EventSubscriber eo) {
        T msg;
        EventSubscriber producer = null;
        synchronized(this) {
            int n = numMsgs;
            if (n > 0) {
                int ic = icons;
                msg = msgs[ic]; msgs[ic]=null;
                icons = (ic + 1) % msgs.length;
                numMsgs = n - 1;
                
                if (srcs.size() > 0) {
                    producer = srcs.poll();
                }
            } else {
                msg = null;
                addMsgAvailableListener(eo);
            }
        }
        if (producer != null)  {
            producer.onEvent(this, spaceAvailble);
        }
        return msg;
    }
    
    /**
     * 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
     * @see #putnb(Object)
     * @see #putb(Object) 
     */
    @SuppressWarnings("unchecked")
    public boolean put(T msg, EventSubscriber eo) {
        boolean ret = true; // assume we will be able to enqueue
        EventSubscriber subscriber;
        synchronized(this) {
            if (msg == null) {
                throw new NullPointerException("Null message supplied to put");
            }
            int ip = iprod;
            int ic = icons;
            int n = numMsgs;
            if (n == msgs.length) {
                assert ic == ip : "numElements == msgs.length && ic != ip";
                if (n < maxMsgs) {
                    T[] newmsgs = (T[]) new Object[Math.min(n * 2, maxMsgs)];
                    System.arraycopy(msgs, ic, newmsgs, 0, n - ic);
                    if (ic > 0) {
                        System.arraycopy(msgs, 0, newmsgs, n - ic, ic);
                    }
                    msgs = newmsgs;
                    ip = n;
                    ic = 0;
                } else {
                    ret = false;
                }
            }
            if (ret) {
                numMsgs = n + 1;
                msgs[ip] = msg;
                iprod = (ip + 1) % msgs.length;
                icons = ic;
                subscriber = sink;
                sink = null;
            } else {
                subscriber = null;
                // unable to enqueue
                if (eo != null) {
                    srcs.add(eo);
                }
            }
        }
        // notify get's subscriber that something is available
        if (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, or null if timed out.
     * @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;
    }
    
    
    /**
     * Block caller until at least one message is available.
     * @throws Pausable
     */
	public void untilHasMessage() throws Pausable {
		while (hasMessage(Task.getCurrentTask()) == false) {
			Task.pause(this);
		}
	}

	/**
	 * Block caller until num messages are available.
	 * @param num
	 * @throws Pausable
	 */
	public void untilHasMessages(int num) throws Pausable {
		while (hasMessages(num, Task.getCurrentTask()) == false) {
			Task.pause(this);
		}
	}

	public boolean hasMessage(Task eo) {
		boolean has_msg;
		synchronized (this) {
			int n = numMsgs;
			if (n > 0) {
				has_msg = true;
			} else {
				has_msg = false;
				addMsgAvailableListener(eo);
			}
		}
		return has_msg;
	}

	public boolean hasMessages(int num, Task eo) {
		boolean has_msg;
		synchronized (this) {
			int n = numMsgs;
			if (n >= num) {
				has_msg = true;
			} else {
				has_msg = false;
				addMsgAvailableListener(eo);
			}
		}
		return has_msg;
	}


	public T peek(int idx) {
		assert idx >= 0 : "negative index";
		T msg;
		synchronized (this) {
			int n = numMsgs;
			if (idx < n) {
				int ic = icons;
				msg = msgs[(ic + idx) % msgs.length];

				assert msg != null : "peeked null message!";
			} else {
				msg = null;
			}
		}
		return msg;
	}

	public T remove(final int idx) {
		assert idx >= 0 : "negative index";
		T msg;
		synchronized (this) {
			int n = numMsgs;
			assert idx < numMsgs;
			if (idx < n) {
				int ic = icons;
				int mlen = msgs.length;
				msg = msgs[(ic + idx) % mlen];
				for (int i = idx; i > 0; i--) {
					msgs[(ic + i) % mlen] = msgs[(ic + i - 1) % mlen];
				}
				msgs[icons] = null;
				numMsgs -= 1;
				icons = (icons + 1) % mlen;
			} else {
				throw new IllegalStateException();
			}
		}
		return msg;
	}

	public synchronized Object[] messages() {
		synchronized (this) {
			Object[] result = new Object[numMsgs];
			for (int i = 0; i < numMsgs; i++) {
				result[i] = msgs[(icons + i) % msgs.length];
			}
			return result;
		}

	}


    /**
     * Takes an array of mailboxes and returns the index of the first mailbox
     * that has a message. It is possible that because of race conditions, an
     * earlier mailbox in the list may also have received a message.
     */
    // TODO: need timeout variant
    @SuppressWarnings("unchecked")
    public static int select(Mailbox... mboxes) throws Pausable {
        while (true) {
            for (int i = 0; i < mboxes.length; i++) {
                if (mboxes[i].hasMessage()) {
                    return i;
                }
            }
            Task t = Task.getCurrentTask();
            EmptySet_MsgAvListener pauseReason = 
                    new EmptySet_MsgAvListener(t, mboxes);
            for (int i = 0; i < mboxes.length; i++) {
                mboxes[i].addMsgAvailableListener(pauseReason);
            }
            Task.pause(pauseReason);
            for (int i = 0; i < mboxes.length; i++) {
                mboxes[i].removeMsgAvailableListener(pauseReason);
            }
        }
    }

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

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


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

    public synchronized void removeMsgAvailableListener(EventSubscriber msgSub) {
        if (sink == msgSub) {
            sink = null;
        }
    }

    /**
     * Attempt to put a message, and return true if successful. The thread is not blocked, nor is the task
     * paused under any circumstance. 
     */
    public boolean putnb(T msg) {
        return put(msg, null);
    }

    /**
     * put a non-null message in the mailbox, and pause the calling task  until the
     * mailbox has space
     */

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

    /**
     * put a non-null message in the mailbox, and pause the calling task  for timeoutMillis
     * if the mailbox is full. 
     */

    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;
        private long current = -1;
        private long tom;
        private long start = 0;

        public BlockingSubscriber(long tom) {
            this.tom = tom;
            if (tom > 0)
                start = current = System.currentTimeMillis();
        }
        
        public void onEvent(EventPublisher ep, Event e) {
            synchronized (Mailbox.this) {
                eventRcvd = true;
                Mailbox.this.notify();
            }
        }
        /** wait for either a timeout or event, returning true if the timeout occurred */        
        public boolean blockingWait() {
            long fin = start + tom;
            synchronized (Mailbox.this) {
                while (!eventRcvd && current < fin) {
                    try {
                        Mailbox.this.wait(tom==0 ? 0 : fin-current);
                    }
                    catch (InterruptedException ie) {}
                    if (tom > 0)
                        current = System.currentTimeMillis();
                }
                if (!eventRcvd)
                    removeSpaceAvailableListener(this);
            }
            return current < fin;
        }
    }
    
    
    /**
     * put a non-null message in the mailbox, and block the calling thread for timeoutMillis
     * if the mailbox is full
     * @return true if the message was successfully put in the mailbox
     */
    public boolean putb(T msg,long timeoutMillis) {
        BlockingSubscriber evs = new BlockingSubscriber(timeoutMillis);
        boolean success;
        while (!(success = put(msg, evs)) && evs.blockingWait()) {}
        return success;
    }

    public synchronized int size() {
        return numMsgs;
    }
    
    public synchronized boolean hasMessage() {
        return numMsgs > 0;
    }

    public synchronized boolean hasSpace() {
        return (maxMsgs - numMsgs) > 0;
    }

    /**
     * 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(timeoutMillis);
        T msg;
        while ((msg = get(evs))==null && evs.blockingWait()) {}
        return msg;
    }

    public synchronized String toString() {
        return "id:" + System.identityHashCode(this) + " " +
        // DEBUG "nGet:" + nGet + " " +
                // "nPut:" + nPut + " " +
                // "numWastedPuts:" + nWastedPuts + " " +
                // "nWastedGets:" + nWastedGets + " " +
                "numMsgs:" + numMsgs;
    }

    // Implementation of PauseReason
    public boolean isValid(Task t) {
        synchronized(this) {
            return ((t == sink) || srcs.contains(t)) && ! t.checkTimeout();
        }
    }
}

class EmptySet_MsgAvListener implements PauseReason, EventSubscriber {
    final Task task;
    final Mailbox[] mbxs;

    EmptySet_MsgAvListener(Task t, Mailbox[] mbs) {
        task = t;
        mbxs = mbs;
    }

    public boolean isValid(Task t) {
        // The pauseReason is true (there is valid reason to continue
        // pausing) if none of the mboxes have any elements
        for (Mailbox mb : mbxs) {
            if (mb.hasMessage())
                return false;
        }
        return true;
    }

    public void onEvent(EventPublisher ep, Event e) {
        for (Mailbox m : mbxs) {
            if (m != ep) {
                ((Mailbox)ep).removeMsgAvailableListener(this);
            }
        }
        task.resume();
    }

    public void cancel() {
        for (Mailbox mb : mbxs) {
            mb.removeMsgAvailableListener(this);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy