/* 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; import java.util.TimerTask; /** * 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<T> implements PauseReason, EventPublisher { T message; EventSubscriber sink; 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(MSG_AVAILABLE); public static final Event messageAvailable = new Event(SPACE_AVAILABLE); public static final Event timedOut = new Event(TIMED_OUT); LinkedList<EventSubscriber> srcs = new LinkedList<EventSubscriber>(); // 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; synchronized(this) { if (message == null) { ret = null; addMsgAvailableListener(eo); } else { ret = message; message = 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 amsg, EventSubscriber eo) { boolean ret = true; // assume we'll be able to enqueue EventSubscriber subscriber; synchronized(this) { if (amsg == null) { throw new NullPointerException("Null message supplied to put"); } if (message == null) { // space available message = amsg; subscriber = sink; sink = null; } else { ret = false; // unable to enqueue. Cell is full subscriber = null; 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. * @throws Pausable */ public T get(long timeoutMillis) throws Pausable { final Task t = Task.getCurrentTask(); T msg = get(t); long begin = System.currentTimeMillis(); while (msg == null) { TimerTask tt = new TimerTask() { public void run() { Cell.this.removeMsgAvailableListener(t); t.onEvent(Cell.this, timedOut); } }; Task.timer.schedule(tt, timeoutMillis); Task.pause(this); tt.cancel(); if (System.currentTimeMillis() - begin > timeoutMillis) { break; } removeMsgAvailableListener(t); msg = get(t); } return msg; } 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) { 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; } } 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(); while (!put(msg, t)) { TimerTask tt = new TimerTask() { public void run() { Cell.this.removeSpaceAvailableListener(t); t.onEvent(Cell.this, timedOut); } }; Task.timer.schedule(tt, timeoutMillis); Task.pause(this); removeSpaceAvailableListener(t); if (System.currentTimeMillis() - begin >= timeoutMillis) { 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 synchronized boolean hasMessage() { return message != null; } public synchronized boolean hasSpace() { return message == 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 synchronized String toString() { return "id:" + System.identityHashCode(this) + " " + message; } // Implementation of PauseReason public boolean isValid(Task t) { synchronized(this) { return (t == sink) || srcs.contains(t); } } }