Package kilim

Source Code of kilim.EmptySet_MsgAvListener

/* 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;

/**
* 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<T> 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(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 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 end = System.currentTimeMillis() + timeoutMillis;
        while (msg == null) {
            TimerTask tt = new TimerTask() {
                public void run() {
                    Mailbox.this.removeMsgAvailableListener(t);
                    t.onEvent(Mailbox.this, timedOut);
                }
            };
            Task.timer.schedule(tt, timeoutMillis);
            Task.pause(this);
            tt.cancel();
            removeMsgAvailableListener(t);
            msg = get(t);
           
            timeoutMillis = end - System.currentTimeMillis();
            if (timeoutMillis <= 0) {
              removeMsgAvailableListener(t);
                break;
            }
        }
        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 <code>num</code> messages are available.
   * @param num
   * @throws Pausable
   */
  public void untilHasMessages(int num) throws Pausable {
    while (hasMessages(num, Task.getCurrentTask()) == false) {
      Task.pause(this);
    }
  }


  /**
   * Block caller (with timeout) until a message is available.
   * @return non-null message.
   * @throws Pausable
   */
  public boolean untilHasMessage(long timeoutMillis) throws Pausable {
    final Task t = Task.getCurrentTask();
    boolean has_msg = hasMessage(t);
    long end = System.currentTimeMillis() + timeoutMillis;
    while (has_msg == false) {
      TimerTask tt = new TimerTask() {
        public void run() {
                    Mailbox.this.removeMsgAvailableListener(t);
                  t.onEvent(Mailbox.this, timedOut);
        }
      };
      Task.timer.schedule(tt, timeoutMillis);
      Task.pause(this);
      tt.cancel();
      has_msg = hasMessage(t);
      timeoutMillis = end - System.currentTimeMillis();
      if (timeoutMillis <= 0) {
              removeMsgAvailableListener(t);
        break;
      }
    }
    return has_msg;
  }

  /**
   * Block caller (with timeout) until <code>num</code> messages are available.
   *
   * @param num
   * @param timeoutMillis
   * @return Message or <code>null</code> on timeout
   * @throws Pausable
   */
  public boolean untilHasMessages(int num, long timeoutMillis)
      throws Pausable {
    final Task t = Task.getCurrentTask();
    final long end = System.currentTimeMillis() + timeoutMillis;

    boolean has_msg = hasMessages(num, t);
    while (has_msg == false) {
      TimerTask tt = new TimerTask() {
        public void run() {
                    Mailbox.this.removeMsgAvailableListener(t);
                  t.onEvent(Mailbox.this, timedOut);
        }
      };
      Task.timer.schedule(tt, timeoutMillis);
      Task.pause(this);
      if (!tt.cancel()) {
              removeMsgAvailableListener(t);
      }

      has_msg = hasMessages(num, t);
      timeoutMillis = end - System.currentTimeMillis();
      if (!has_msg && timeoutMillis <= 0) {
              removeMsgAvailableListener(t);
        break;
      }
    }
    return has_msg;
  }

  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();
        while (!put(msg, t)) {
            TimerTask tt = new TimerTask() {
                public void run() {
                    Mailbox.this.removeSpaceAvailableListener(t);
                    t.onEvent(Mailbox.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 (Mailbox.this) {
                eventRcvd = true;
                Mailbox.this.notify();
            }
        }
        public void blockingWait(final long timeoutMillis) {
            long start = System.currentTimeMillis();
            long remaining = timeoutMillis;
            boolean infiniteWait = timeoutMillis == 0;
            synchronized (Mailbox.this) {
                while (!eventRcvd && (infiniteWait || remaining > 0)) {
                    try {
                        Mailbox.this.wait(infiniteWait? 0 : remaining);
                    } catch (InterruptedException ie) {}
                    long elapsed = System.currentTimeMillis() - start;
                    remaining -= elapsed;
                }
            }
        }
    }
   
   
    /**
     * put a non-null message in the mailbox, and block the calling thread  for timeoutMillis
     * if the mailbox is full.
     */
    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 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();
        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) + " " +
        // 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);
        }
    }
}

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);
        }
    }
}
TOP

Related Classes of kilim.EmptySet_MsgAvListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.