PageRenderTime 57ms CodeModel.GetById 15ms app.highlight 37ms RepoModel.GetById 1ms app.codeStats 0ms

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