PageRenderTime 66ms CodeModel.GetById 12ms app.highlight 48ms RepoModel.GetById 1ms app.codeStats 0ms

/src/kilim/Mailbox.java

http://github.com/kilim/kilim
Java | 539 lines | 375 code | 58 blank | 106 comment | 72 complexity | e4297af70c50463b4931e601080d108e MD5 | raw file
  1/* Copyright (c) 2006, Sriram Srinivasan
  2 *
  3 * You may distribute this software under the terms of the license 
  4 * specified in the file "License"
  5 */
  6
  7package kilim;
  8
  9import java.util.LinkedList;
 10
 11/**
 12 * This is a typed buffer that supports multiple producers and a single
 13 * consumer. It is the basic construct used for tasks to interact and
 14 * synchronize with each other (as opposed to direct java calls or static member
 15 * variables). put() and get() are the two essential functions.
 16 * 
 17 * We use the term "block" to mean thread block, and "pause" to mean
 18 * fiber pausing. The suffix "nb" on some methods (such as getnb())
 19 * stands for non-blocking. Both put() and get() have blocking and
 20 * non-blocking variants in the form of putb(), putnb
 21 */
 22
 23public class Mailbox<T> implements PauseReason, EventPublisher {
 24    // TODO. Give mbox a config name and id and make monitorable
 25    T[] msgs;
 26    private int iprod = 0; // producer index
 27    private int icons = 0; // consumer index;
 28    private int numMsgs = 0;
 29    private int maxMsgs = 300;
 30    EventSubscriber sink;
 31    
 32    // FIX: I don't like this event design. The only good thing is that
 33    // we don't create new event objects every time we signal a client
 34    // (subscriber) that's blocked on this mailbox.
 35    public static final int SPACE_AVAILABLE = 1;
 36    public static final int MSG_AVAILABLE = 2;
 37    public static final int TIMED_OUT = 3;
 38    public static final Event spaceAvailble = new Event(SPACE_AVAILABLE);
 39    public static final Event messageAvailable = new Event(MSG_AVAILABLE);
 40    public static final Event timedOut = new Event(TIMED_OUT);
 41    
 42    LinkedList<EventSubscriber> srcs = new LinkedList<EventSubscriber>();
 43
 44    // DEBUG stuff
 45    // To do: move into monitorable stat object
 46    /*
 47     * public int nPut = 0; public int nGet = 0; public int nWastedPuts = 0;
 48     * public int nWastedGets = 0;
 49     */
 50    public Mailbox() {
 51        this(10);
 52    }
 53
 54    public Mailbox(int initialSize) {
 55        this(initialSize, Integer.MAX_VALUE);
 56    }
 57
 58    @SuppressWarnings("unchecked")
 59    public Mailbox(int initialSize, int maxSize) {
 60        if (initialSize > maxSize)
 61            throw new IllegalArgumentException("initialSize: " + initialSize
 62                    + " cannot exceed maxSize: " + maxSize);
 63        msgs = (T[]) new Object[initialSize];
 64        maxMsgs = maxSize;
 65    }
 66
 67    /**
 68     * Non-blocking, nonpausing get. 
 69     * @param eo. If non-null, registers this observer and calls it with a MessageAvailable event when
 70     *  a put() is done.
 71     * @return buffered message if there's one, or null 
 72     */
 73    public T get(EventSubscriber eo) {
 74        T msg;
 75        EventSubscriber producer = null;
 76        synchronized(this) {
 77            int n = numMsgs;
 78            if (n > 0) {
 79                int ic = icons;
 80                msg = msgs[ic]; msgs[ic]=null;
 81                icons = (ic + 1) % msgs.length;
 82                numMsgs = n - 1;
 83                
 84                if (srcs.size() > 0) {
 85                    producer = srcs.poll();
 86                }
 87            } else {
 88                msg = null;
 89                addMsgAvailableListener(eo);
 90            }
 91        }
 92        if (producer != null)  {
 93            producer.onEvent(this, spaceAvailble);
 94        }
 95        return msg;
 96    }
 97    
 98    /**
 99     * Non-blocking, nonpausing put. 
100     * @param eo. If non-null, registers this observer and calls it with an SpaceAvailable event 
101     * when there's space.
102     * @return buffered message if there's one, or null
103     * @see #putnb(Object)
104     * @see #putb(Object) 
105     */
106    @SuppressWarnings("unchecked")
107    public boolean put(T msg, EventSubscriber eo) {
108        boolean ret = true; // assume we will be able to enqueue
109        EventSubscriber subscriber;
110        synchronized(this) {
111            if (msg == null) {
112                throw new NullPointerException("Null message supplied to put");
113            }
114            int ip = iprod;
115            int ic = icons;
116            int n = numMsgs;
117            if (n == msgs.length) {
118                assert ic == ip : "numElements == msgs.length && ic != ip";
119                if (n < maxMsgs) {
120                    T[] newmsgs = (T[]) new Object[Math.min(n * 2, maxMsgs)];
121                    System.arraycopy(msgs, ic, newmsgs, 0, n - ic);
122                    if (ic > 0) {
123                        System.arraycopy(msgs, 0, newmsgs, n - ic, ic);
124                    }
125                    msgs = newmsgs;
126                    ip = n;
127                    ic = 0;
128                } else {
129                    ret = false;
130                }
131            }
132            if (ret) {
133                numMsgs = n + 1;
134                msgs[ip] = msg;
135                iprod = (ip + 1) % msgs.length;
136                icons = ic;
137                subscriber = sink;
138                sink = null;
139            } else {
140                subscriber = null;
141                // unable to enqueue
142                if (eo != null) {
143                    srcs.add(eo);
144                }
145            }
146        }
147        // notify get's subscriber that something is available
148        if (subscriber != null) {
149            subscriber.onEvent(this, messageAvailable);
150        }
151        return ret;
152    }
153    
154    /**
155     * Get, don't pause or block.
156     * 
157     * @return stored message, or null if no message found.
158     */
159    public T getnb() {
160        return get(null);
161    }
162
163    /**
164     * @return non-null message.
165     * @throws Pausable
166     */
167    public T get() throws Pausable{
168        Task t = Task.getCurrentTask();
169        T msg = get(t);
170        while (msg == null) {
171            Task.pause(this);
172            removeMsgAvailableListener(t);
173            msg = get(t);
174        }
175        return msg;
176    }
177
178    
179    /**
180     * @return non-null message, or null if timed out.
181     * @throws Pausable
182     */
183    public T get(long timeoutMillis) throws Pausable {
184        final Task t = Task.getCurrentTask();
185        T msg = get(t);
186        long begin = System.currentTimeMillis();
187        long time = timeoutMillis;
188        while (msg == null) {
189            t.timer.setTimer(time);
190            t.scheduler.scheduleTimer(t.timer);
191            Task.pause(this);
192            t.timer.cancel();
193            removeMsgAvailableListener(t);
194            time = timeoutMillis - (System.currentTimeMillis() - begin);
195            if (time <= 0) {
196                break;
197            }
198            msg = get(t);
199        }
200        return msg;
201    }
202    
203    
204    /**
205     * Block caller until at least one message is available.
206     * @throws Pausable
207     */
208	public void untilHasMessage() throws Pausable {
209		while (hasMessage(Task.getCurrentTask()) == false) {
210			Task.pause(this);
211		}
212	}
213
214	/**
215	 * Block caller until <code>num</code> messages are available.
216	 * @param num
217	 * @throws Pausable
218	 */
219	public void untilHasMessages(int num) throws Pausable {
220		while (hasMessages(num, Task.getCurrentTask()) == false) {
221			Task.pause(this);
222		}
223	}
224
225	public boolean hasMessage(Task eo) {
226		boolean has_msg;
227		synchronized (this) {
228			int n = numMsgs;
229			if (n > 0) {
230				has_msg = true;
231			} else {
232				has_msg = false;
233				addMsgAvailableListener(eo);
234			}
235		}
236		return has_msg;
237	}
238
239	public boolean hasMessages(int num, Task eo) {
240		boolean has_msg;
241		synchronized (this) {
242			int n = numMsgs;
243			if (n >= num) {
244				has_msg = true;
245			} else {
246				has_msg = false;
247				addMsgAvailableListener(eo);
248			}
249		}
250		return has_msg;
251	}
252
253
254	public T peek(int idx) {
255		assert idx >= 0 : "negative index";
256		T msg;
257		synchronized (this) {
258			int n = numMsgs;
259			if (idx < n) {
260				int ic = icons;
261				msg = msgs[(ic + idx) % msgs.length];
262
263				assert msg != null : "peeked null message!";
264			} else {
265				msg = null;
266			}
267		}
268		return msg;
269	}
270
271	public T remove(final int idx) {
272		assert idx >= 0 : "negative index";
273		T msg;
274		synchronized (this) {
275			int n = numMsgs;
276			assert idx < numMsgs;
277			if (idx < n) {
278				int ic = icons;
279				int mlen = msgs.length;
280				msg = msgs[(ic + idx) % mlen];
281				for (int i = idx; i > 0; i--) {
282					msgs[(ic + i) % mlen] = msgs[(ic + i - 1) % mlen];
283				}
284				msgs[icons] = null;
285				numMsgs -= 1;
286				icons = (icons + 1) % mlen;
287			} else {
288				throw new IllegalStateException();
289			}
290		}
291		return msg;
292	}
293
294	public synchronized Object[] messages() {
295		synchronized (this) {
296			Object[] result = new Object[numMsgs];
297			for (int i = 0; i < numMsgs; i++) {
298				result[i] = msgs[(icons + i) % msgs.length];
299			}
300			return result;
301		}
302
303	}
304
305
306    /**
307     * Takes an array of mailboxes and returns the index of the first mailbox
308     * that has a message. It is possible that because of race conditions, an
309     * earlier mailbox in the list may also have received a message.
310     */
311    // TODO: need timeout variant
312    @SuppressWarnings("unchecked")
313    public static int select(Mailbox... mboxes) throws Pausable {
314        while (true) {
315            for (int i = 0; i < mboxes.length; i++) {
316                if (mboxes[i].hasMessage()) {
317                    return i;
318                }
319            }
320            Task t = Task.getCurrentTask();
321            EmptySet_MsgAvListener pauseReason = 
322                    new EmptySet_MsgAvListener(t, mboxes);
323            for (int i = 0; i < mboxes.length; i++) {
324                mboxes[i].addMsgAvailableListener(pauseReason);
325            }
326            Task.pause(pauseReason);
327            for (int i = 0; i < mboxes.length; i++) {
328                mboxes[i].removeMsgAvailableListener(pauseReason);
329            }
330        }
331    }
332
333    public synchronized void addSpaceAvailableListener(EventSubscriber spcSub) {
334        srcs.add(spcSub);
335    }
336
337    public synchronized void removeSpaceAvailableListener(EventSubscriber spcSub) {
338        srcs.remove(spcSub);
339    }
340
341
342    public synchronized void addMsgAvailableListener(EventSubscriber msgSub) {
343        if (sink != null && sink != msgSub) {
344            throw new AssertionError(
345                    "Error: A mailbox can not be shared by two consumers.  New = "
346                            + msgSub + ", Old = " + sink);
347        }
348        sink = msgSub;
349    }
350
351    public synchronized void removeMsgAvailableListener(EventSubscriber msgSub) {
352        if (sink == msgSub) {
353            sink = null;
354        }
355    }
356
357    /**
358     * Attempt to put a message, and return true if successful. The thread is not blocked, nor is the task
359     * paused under any circumstance. 
360     */
361    public boolean putnb(T msg) {
362        return put(msg, null);
363    }
364
365    /**
366     * put a non-null message in the mailbox, and pause the calling task  until the
367     * mailbox has space
368     */
369
370    public void put(T msg) throws Pausable {
371        Task t = Task.getCurrentTask();
372        while (!put(msg, t)) {
373            Task.pause(this);
374            removeSpaceAvailableListener(t);
375        }
376    }
377
378    /**
379     * put a non-null message in the mailbox, and pause the calling task  for timeoutMillis
380     * if the mailbox is full. 
381     */
382
383    public boolean put(T msg, int timeoutMillis) throws Pausable {
384        final Task t = Task.getCurrentTask();
385        long begin = System.currentTimeMillis();
386        long time = timeoutMillis;
387        while (!put(msg,t)) {
388            t.timer.setTimer(time);
389            t.scheduler.scheduleTimer(t.timer);
390            Task.pause(this);
391            t.timer.cancel();
392            removeSpaceAvailableListener(t);
393            time = timeoutMillis-(System.currentTimeMillis()-begin);
394            if (time<=0)
395                return false;
396        }
397        return true;
398    }
399    
400    public void putb(T msg) {
401        putb(msg, 0 /* infinite wait */);
402    }
403
404    public class BlockingSubscriber implements EventSubscriber {
405        public volatile boolean eventRcvd = false;
406        private long current = -1;
407        private long tom;
408        private long start = 0;
409
410        public BlockingSubscriber(long tom) {
411            this.tom = tom;
412            if (tom > 0)
413                start = current = System.currentTimeMillis();
414        }
415        
416        public void onEvent(EventPublisher ep, Event e) {
417            synchronized (Mailbox.this) {
418                eventRcvd = true;
419                Mailbox.this.notify();
420            }
421        }
422        /** wait for either a timeout or event, returning true if the timeout occurred */        
423        public boolean blockingWait() {
424            long fin = start + tom;
425            synchronized (Mailbox.this) {
426                while (!eventRcvd && current < fin) {
427                    try {
428                        Mailbox.this.wait(tom==0 ? 0 : fin-current);
429                    }
430                    catch (InterruptedException ie) {}
431                    if (tom > 0)
432                        current = System.currentTimeMillis();
433                }
434                if (!eventRcvd)
435                    removeSpaceAvailableListener(this);
436            }
437            return current < fin;
438        }
439    }
440    
441    
442    /**
443     * put a non-null message in the mailbox, and block the calling thread for timeoutMillis
444     * if the mailbox is full
445     * @return true if the message was successfully put in the mailbox
446     */
447    public boolean putb(T msg,long timeoutMillis) {
448        BlockingSubscriber evs = new BlockingSubscriber(timeoutMillis);
449        boolean success;
450        while (!(success = put(msg, evs)) && evs.blockingWait()) {}
451        return success;
452    }
453
454    public synchronized int size() {
455        return numMsgs;
456    }
457    
458    public synchronized boolean hasMessage() {
459        return numMsgs > 0;
460    }
461
462    public synchronized boolean hasSpace() {
463        return (maxMsgs - numMsgs) > 0;
464    }
465
466    /**
467     * retrieve a message, blocking the thread indefinitely. Note, this is a
468     * heavyweight block, unlike #get() that pauses the Fiber but doesn't block
469     * the thread.
470     */
471
472    public T getb() {
473        return getb(0);
474    }
475
476    /**
477     * retrieve a msg, and block the Java thread for the time given.
478     * 
479     * @param millis. max wait time
480     * @return null if timed out.
481     */
482    public T getb(final long timeoutMillis) {
483        BlockingSubscriber evs = new BlockingSubscriber(timeoutMillis);
484        T msg;
485        while ((msg = get(evs))==null && evs.blockingWait()) {}
486        return msg;
487    }
488
489    public synchronized String toString() {
490        return "id:" + System.identityHashCode(this) + " " +
491        // DEBUG "nGet:" + nGet + " " +
492                // "nPut:" + nPut + " " +
493                // "numWastedPuts:" + nWastedPuts + " " +
494                // "nWastedGets:" + nWastedGets + " " +
495                "numMsgs:" + numMsgs;
496    }
497
498    // Implementation of PauseReason
499    public boolean isValid(Task t) {
500        synchronized(this) {
501            return ((t == sink) || srcs.contains(t)) && ! t.checkTimeout();
502        }
503    }
504}
505
506class EmptySet_MsgAvListener implements PauseReason, EventSubscriber {
507    final Task task;
508    final Mailbox<?>[] mbxs;
509
510    EmptySet_MsgAvListener(Task t, Mailbox<?>[] mbs) {
511        task = t;
512        mbxs = mbs;
513    }
514
515    public boolean isValid(Task t) {
516        // The pauseReason is true (there is valid reason to continue
517        // pausing) if none of the mboxes have any elements
518        for (Mailbox<?> mb : mbxs) {
519            if (mb.hasMessage())
520                return false;
521        }
522        return true;
523    }
524
525    public void onEvent(EventPublisher ep, Event e) {
526        for (Mailbox<?> m : mbxs) {
527            if (m != ep) {
528                ((Mailbox<?>)ep).removeMsgAvailableListener(this);
529            }
530        }
531        task.resume();
532    }
533
534    public void cancel() {
535        for (Mailbox<?> mb : mbxs) {
536            mb.removeMsgAvailableListener(this);
537        }
538    }
539}