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