/src/kilim/Mailbox.java
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}