/src/kilim/nio/EndPoint.java

http://github.com/kilim/kilim · Java · 256 lines · 158 code · 21 blank · 77 comment · 28 complexity · 3bf895d2a8b99a5ff2612536fb51931d 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.nio;
  7. import java.io.EOFException;
  8. import java.io.IOException;
  9. import java.nio.ByteBuffer;
  10. import java.nio.channels.FileChannel;
  11. import java.nio.channels.SelectionKey;
  12. import java.nio.channels.SocketChannel;
  13. import java.nio.channels.spi.AbstractSelectableChannel;
  14. import kilim.Mailbox;
  15. import kilim.Pausable;
  16. import kilim.Task;
  17. /**
  18. * The EndPoint represents an open socket connection. It is a wrapper over a non-blocking socket (channel) and belongs
  19. * to a {@link SessionTask}. It serves as the bridge between the SessionTask and the {@link NioSelectorScheduler}, using
  20. * a pair of mailboxes for exchanging socket registration and socket ready events.
  21. * <p>
  22. * The other purpose of this class is to provide convenience methods that read from a socket into a bytebuffer, or write
  23. * from a bytebuffer to the socket. If the socket is not ready for business, the endpoint (and hence the task) simply
  24. * yields, <i>without</i> registering with the {@link NioSelectorScheduler}. The idea is to give the other runnable
  25. * tasks a chance to run before retrying the operation (on resumption); this avoids waking up the selector -- an
  26. * expensive operation -- as much as possible, and introduces a delay between retries. If, after a fixed number of times
  27. * ({@link #YIELD_COUNT}), the operation still hasn't succeeded, the endpoint registers itself with the
  28. * {@link NioSelectorScheduler}, and waits for a socket-ready event from the selector.
  29. *
  30. * This scheme is adaptive to load, in that the delay between retries is proportional to the number of runnable tasks.
  31. * Busy sockets tend to get serviced more often as the socket is always ready.
  32. */
  33. public class EndPoint { // Mailbox for receiving socket ready events.
  34. // TODO: This too must be made adaptive.
  35. static final int YIELD_COUNT = Integer.parseInt(System.getProperty("kilim.nio.yieldCount", "4"));
  36. /**
  37. * The socket channel wrapped by the EndPoint. See #dataChannel()
  38. */
  39. public AbstractSelectableChannel sockch;
  40. private Mailbox<SockEvent> box = new Mailbox<SockEvent>();
  41. private NioSelectorScheduler sched;
  42. public EndPoint(NioSelectorScheduler nio,AbstractSelectableChannel ch) {
  43. this.sockch = ch;
  44. this.sched = nio;
  45. }
  46. public SocketChannel dataChannel() {
  47. return (SocketChannel) sockch;
  48. }
  49. /**
  50. * Write buf.remaining() bytes to dataChannel().
  51. */
  52. public void write(ByteBuffer buf) throws IOException, Pausable {
  53. SocketChannel ch = dataChannel();
  54. int remaining = buf.remaining();
  55. if (remaining == 0)
  56. return;
  57. int n = ch.write(buf);
  58. remaining -= n;
  59. int yieldCount = 0;
  60. while (remaining > 0) {
  61. if (n == 0) {
  62. yieldCount++;
  63. if (yieldCount < YIELD_COUNT) {
  64. Task.yield(); // don't go back to selector yet.
  65. } else {
  66. pauseUntilWritable();
  67. yieldCount = 0;
  68. }
  69. }
  70. n = ch.write(buf);
  71. remaining -= n;
  72. }
  73. }
  74. /**
  75. * Read <code>atleastN</code> bytes more into the buffer if there's space. Otherwise, allocate a bigger
  76. * buffer that'll accomodate the earlier contents and atleastN more bytes.
  77. *
  78. * @param buf
  79. * ByteBuffer to be filled
  80. * @param atleastN
  81. * At least this many bytes to be read.
  82. * @throws IOException
  83. */
  84. public ByteBuffer fill(ByteBuffer buf, int atleastN) throws IOException, Pausable {
  85. if (buf.remaining() < atleastN) {
  86. ByteBuffer newbb = ByteBuffer.allocate(Math.max(buf.capacity() * 3 / 2, buf.position() + atleastN));
  87. buf.rewind();
  88. newbb.put(buf);
  89. buf = newbb;
  90. }
  91. SocketChannel ch = dataChannel();
  92. if (!ch.isOpen()) {
  93. throw new EOFException();
  94. }
  95. int yieldCount = 0;
  96. do {
  97. int n = ch.read(buf);
  98. // System.out.println(buf);
  99. if (n == -1) {
  100. IOException ex = close2();
  101. if (ex != null)
  102. Sched.log(Task.getCurrentTask().getScheduler(),this,ex);
  103. throw new EOFException();
  104. }
  105. if (n == 0) {
  106. yieldCount++;
  107. if (yieldCount < YIELD_COUNT) {
  108. // Avoid registering with the selector because it requires waking up the selector, context switching
  109. // between threads and calling the OS just to register. Just yield, let other tasks have a go, then
  110. // check later. Do this at most YIELD_COUNT times before going back to the selector.
  111. Task.yield();
  112. } else {
  113. pauseUntilReadable();
  114. yieldCount = 0;
  115. }
  116. }
  117. atleastN -= n;
  118. } while (atleastN > 0);
  119. return buf;
  120. }
  121. private static class Sched extends kilim.AffineScheduler {
  122. static void log(kilim.Scheduler sched,Object src,Object obj) { logRelay(sched,src,obj); }
  123. }
  124. /**
  125. * Reads a length-prefixed message in its entirety.
  126. *
  127. * @param bb The bytebuffer to fill, assuming there is sufficient space (including the bytes for the length). Otherwise, the
  128. * contents are copied into a sufficiently big buffer, and the new buffer is returned.
  129. *
  130. * @param lengthLength The number of bytes occupied by the length. Must be 1, 2 or 4, assumed to be in big-endian order.
  131. * @param lengthIncludesItself true if the packet length includes lengthLength
  132. * @return the buffer bb passed in if the message fits or a new buffer. Either way, the buffer returned has the entire
  133. * message including the initial length prefix.
  134. * @throws IOException
  135. * @throws Pausable
  136. */
  137. public ByteBuffer fillMessage(ByteBuffer bb, int lengthLength, boolean lengthIncludesItself) throws IOException, Pausable {
  138. int pos = bb.position();
  139. int opos = pos; // save orig pos
  140. bb = fill(bb, lengthLength);
  141. byte a, b, c, d;
  142. a = b = c = d = 0;
  143. switch (lengthLength) {
  144. case 4: a = bb.get(pos); pos++;
  145. b = bb.get(pos); pos++; // fall through
  146. case 2: c = bb.get(pos); pos++; // fall through
  147. case 1: d = bb.get(pos); break;
  148. default: throw new IllegalArgumentException("Incorrect lengthLength (may only be 1, 2 or 4): " + lengthLength);
  149. }
  150. int contentLen = ((a << 24) + (b << 16) + (c << 8) + (d << 0));
  151. // TODO: put a limit on len
  152. if (lengthIncludesItself) {
  153. contentLen -= lengthLength;
  154. }
  155. // If the fill() above hasn't read in all the content, read the remaining
  156. int remaining = contentLen - (bb.position() - opos - lengthLength);
  157. if (remaining > 0) {
  158. bb = fill(bb, remaining);
  159. }
  160. return bb;
  161. }
  162. // TODO. Need to introduce session timeouts for read and write
  163. public void pauseUntilReadable() throws Pausable, IOException {
  164. SockEvent ev = new SockEvent(box, sockch, SelectionKey.OP_READ);
  165. sched.regbox.putnb(ev);
  166. box.get();
  167. }
  168. public void pauseUntilWritable() throws Pausable, IOException {
  169. SockEvent ev = new SockEvent(box, sockch, SelectionKey.OP_WRITE);
  170. sched.regbox.putnb(ev);
  171. box.get();
  172. }
  173. public void pauseUntilAcceptable() throws Pausable, IOException {
  174. SockEvent ev = new SockEvent(box, sockch, SelectionKey.OP_ACCEPT);
  175. sched.regbox.putnb(ev);
  176. box.get();
  177. }
  178. /**
  179. * Write a file to the endpoint using {@link FileChannel#transferTo}
  180. *
  181. * @param fc FileChannel to copy to endpoint
  182. * @param start Start offset
  183. * @param length Number of bytes to be written
  184. * @throws IOException
  185. * @throws Pausable
  186. */
  187. public void write(FileChannel fc, long start, long length) throws IOException, Pausable {
  188. SocketChannel ch = dataChannel();
  189. long remaining = length - start;
  190. if (remaining == 0)
  191. return;
  192. long n = fc.transferTo(start, remaining, ch);
  193. start += n;
  194. remaining -= n;
  195. int yieldCount = 0;
  196. while (remaining > 0) {
  197. if (n == 0) {
  198. yieldCount++;
  199. if (yieldCount < YIELD_COUNT) {
  200. Task.yield(); // don't go back to selector yet.
  201. } else {
  202. pauseUntilWritable();
  203. yieldCount = 0;
  204. }
  205. }
  206. n = fc.transferTo(start, remaining, ch);
  207. start += n;
  208. remaining -= n;
  209. }
  210. }
  211. /** Close the endpoint */
  212. IOException close2() {
  213. try { sockch.close(); return null; }
  214. catch (IOException ex) { return ex; }
  215. }
  216. /**
  217. * Close the endpoint, printing and returning and any IOException
  218. * @deprecated in a future version, this method will no longer print the stack trace
  219. */
  220. @Deprecated
  221. public IOException close() {
  222. try {
  223. // if (sk != null && sk.isValid()) {
  224. // sk.attach(null);
  225. // sk.cancel();
  226. // sk = null;
  227. // }
  228. sockch.close();
  229. return null;
  230. } catch (IOException ignore) {
  231. ignore.printStackTrace();
  232. return ignore;
  233. }
  234. }
  235. }