Package net.gleamynode.netty.channel.socket.nio

Source Code of net.gleamynode.netty.channel.socket.nio.NioWorker

/*
* Copyright (C) 2008  Trustin Heuiseung Lee
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA
*/
package net.gleamynode.netty.channel.socket.nio;

import static net.gleamynode.netty.channel.Channels.*;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.gleamynode.netty.buffer.ChannelBuffer;
import net.gleamynode.netty.buffer.ChannelBuffers;
import net.gleamynode.netty.channel.Channel;
import net.gleamynode.netty.channel.ChannelException;
import net.gleamynode.netty.channel.ChannelFuture;
import net.gleamynode.netty.util.NamePreservingRunnable;

class NioWorker implements Runnable {

    private static final Logger logger = Logger.getLogger(NioWorker.class.getName());

    private final int bossId;
    private final int id;
    private final Executor executor;
    private final AtomicBoolean started = new AtomicBoolean();
    volatile Thread thread;
    volatile Selector selector;
    final Object selectorGuard = new Object();

    NioWorker(int bossId, int id, Executor executor) {
        this.bossId = bossId;
        this.id = id;
        this.executor = executor;
    }

    void register(NioSocketChannel channel) {
        boolean firstChannel = started.compareAndSet(false, true);
        Selector selector;
        if (firstChannel) {
            try {
                this.selector = selector = Selector.open();
            } catch (IOException e) {
                throw new ChannelException(
                        "Failed to create a selector.", e);
            }
        } else {
            selector = this.selector;
            if (selector == null) {
                do {
                    Thread.yield();
                    selector = this.selector;
                } while (selector == null);
            }
        }

        if (firstChannel) {
            try {
                channel.socket.register(selector, SelectionKey.OP_READ, channel);
            } catch (ClosedChannelException e) {
                throw new ChannelException(
                        "Failed to register a socket to the selector.", e);
            }

            boolean server = !(channel instanceof NioClientSocketChannel);
            if (server) {
                fireChannelOpen(channel);
            }

            fireChannelBound(channel, channel.getLocalAddress());
            fireChannelConnected(channel, channel.getRemoteAddress());

            String threadName =
                (server ? "New I/O server worker #"
                        : "New I/O client worker #") + bossId + '-' + id;

            executor.execute(new NamePreservingRunnable(this, threadName));
        } else {
            synchronized (selectorGuard) {
                selector.wakeup();
                try {
                    channel.socket.register(selector, SelectionKey.OP_READ, channel);
                } catch (ClosedChannelException e) {
                    throw new ChannelException(
                            "Failed to register a socket to the selector.", e);
                }

                fireChannelOpen(channel);
                fireChannelBound(channel, channel.getLocalAddress());
                fireChannelConnected(channel, channel.getRemoteAddress());
            }
        }
    }

    public void run() {
        thread = Thread.currentThread();

        boolean shutdown = false;
        Selector selector = this.selector;
        for (;;) {
            synchronized (selectorGuard) {
                // This empty synchronization block prevents the selector
                // from acquiring its lock.
            }
            try {
                int selectedKeyCount = selector.select(500);
                if (selectedKeyCount > 0) {
                    processSelectedKeys(selector.selectedKeys());
                }

                if (selector.keys().isEmpty()) {
                    if (shutdown) {
                        try {
                            selector.close();
                        } catch (IOException e) {
                            logger.log(
                                    Level.WARNING,
                                    "Failed to close a selector.", e);
                        } finally {
                            this.selector = null;
                        }
                        started.set(false);
                        break;
                    } else {
                        // Give one more second.
                        shutdown = true;
                    }
                }
            } catch (Throwable t) {
                logger.log(
                        Level.WARNING,
                        "Unexpected exception in the selector loop.", t);

                // Prevent possible consecutive immediate failures.
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // Ignore.
                }
            }
        }
    }

    private static void processSelectedKeys(Set<SelectionKey> selectedKeys) {
        for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext();) {
            SelectionKey k = i.next();
            i.remove();

            if (!k.isValid()) {
                close(k);
                continue;
            }

            if (k.isReadable()) {
                read(k);
            }

            if (!k.isValid()) {
                close(k);
                continue;
            }

            if (k.isWritable()) {
                write(k);
            }
        }
    }

    private static void read(SelectionKey k) {
        ReadableByteChannel ch = (ReadableByteChannel) k.channel();
        NioSocketChannel channel = (NioSocketChannel) k.attachment();

        ReceiveBufferSizePredictor predictor =
            channel.getConfig().getReceiveBufferSizePredictor();
        ByteBuffer buf = ByteBuffer.allocate(predictor.nextReceiveBufferSize());

        int ret = 0;
        int readBytes = 0;
        boolean failure = true;
        try {
            while ((ret = ch.read(buf)) > 0) {
                readBytes += ret;
                if (!buf.hasRemaining()) {
                    break;
                }
            }
            failure = false;
        } catch (Throwable t) {
            fireExceptionCaught(channel, t);
        }

        if (readBytes > 0) {
            // Update the predictor.
            predictor.previousReceiveBufferSize(readBytes);

            // Fire the event.
            ChannelBuffer buffer;
            if (readBytes == buf.capacity()) {
                buffer = ChannelBuffers.wrappedBuffer(buf.array());
            } else {
                buffer = ChannelBuffers.wrappedBuffer(buf.array(), 0, readBytes);
            }
            fireMessageReceived(channel, buffer);
        }

        if (ret < 0 || failure) {
            close(k);
        }
    }

    private static void write(SelectionKey k) {
        NioSocketChannel ch = (NioSocketChannel) k.attachment();
        write(ch);
    }

    private static void close(SelectionKey k) {
        NioSocketChannel ch = (NioSocketChannel) k.attachment();
        close(ch, ch.getSucceededFuture());
    }

    static void write(NioSocketChannel channel) {
        if (channel.writeBuffer.isEmpty() && channel.currentWriteEvent == null) {
            return;
        }

        boolean addOpWrite = false;
        boolean removeOpWrite = false;

        final int maxWrittenBytes;
        if (channel.getConfig().isReadWriteFair()) {
            // Set limitation for the number of written bytes for read-write
            // fairness.  I used maxReadBufferSize * 3 / 2, which yields best
            // performance in my experience while not breaking fairness much.
            int previousReceiveBufferSize =
                channel.getConfig().getReceiveBufferSizePredictor().nextReceiveBufferSize();
            maxWrittenBytes = previousReceiveBufferSize + previousReceiveBufferSize >>> 1;
        } else {
            maxWrittenBytes = Integer.MAX_VALUE;
        }
        int writtenBytes = 0;

        synchronized (channel.writeBuffer) {
            for (;;) {
                if (channel.writeBuffer.isEmpty() && channel.currentWriteEvent == null) {
                    removeOpWrite = true;
                    break;
                }

                ChannelBuffer a;
                if (channel.currentWriteEvent == null) {
                    channel.currentWriteEvent = channel.writeBuffer.poll();
                    a = (ChannelBuffer) channel.currentWriteEvent.getMessage();
                    channel.currentWriteIndex = a.readerIndex();
                } else {
                    a = (ChannelBuffer) channel.currentWriteEvent.getMessage();
                }

                int localWrittenBytes = 0;
                try {
                    for (int i = channel.getConfig().getWriteSpinCount(); i > 0; i --) {
                        localWrittenBytes = a.getBytes(
                            channel.currentWriteIndex,
                            channel.socket,
                            Math.min(maxWrittenBytes - writtenBytes, a.writerIndex() - channel.currentWriteIndex));
                        if (localWrittenBytes != 0) {
                            break;
                        }
                    }
                } catch (Throwable t) {
                    channel.currentWriteEvent.getFuture().setFailure(t);
                    fireExceptionCaught(channel, t);
                }

                writtenBytes += localWrittenBytes;
                channel.currentWriteIndex += localWrittenBytes;
                if (channel.currentWriteIndex == a.writerIndex()) {
                    channel.currentWriteEvent.getFuture().setSuccess();
                    channel.currentWriteEvent = null;
                } else if (localWrittenBytes == 0 || writtenBytes < maxWrittenBytes) {
                    addOpWrite = true;
                    break;
                }
            }
        }

        if (addOpWrite) {
            setOpWrite(channel, true);
        } else if (removeOpWrite) {
            setOpWrite(channel, false);
        }
    }

    private static void setOpWrite(NioSocketChannel channel, boolean opWrite) {
        NioWorker worker = channel.getWorker();
        if (worker == null) {
            IllegalStateException cause =
                new IllegalStateException("Channel not connected yet (null worker)");
            fireExceptionCaught(channel, cause);
            return;
        }

        Selector selector = worker.selector;
        SelectionKey key = channel.socket.keyFor(selector);
        if (!key.isValid()) {
            close(key);
            return;
        }
        int interestOps;
        boolean changed = false;
        if (opWrite) {
            if (Thread.currentThread() == worker.thread) {
                interestOps = key.interestOps();
                if ((interestOps & SelectionKey.OP_WRITE) == 0) {
                    interestOps |= SelectionKey.OP_WRITE;
                    key.interestOps(interestOps);
                    changed = true;
                }
            } else {
                synchronized (worker.selectorGuard) {
                    selector.wakeup();
                    interestOps = key.interestOps();
                    if ((interestOps & SelectionKey.OP_WRITE) == 0) {
                        interestOps |= SelectionKey.OP_WRITE;
                        key.interestOps(interestOps);
                        changed = true;
                    }
                }
            }
        } else {
            if (Thread.currentThread() == worker.thread) {
                interestOps = key.interestOps();
                if ((interestOps & SelectionKey.OP_WRITE) != 0) {
                    interestOps &= ~SelectionKey.OP_WRITE;
                    key.interestOps(interestOps);
                    changed = true;
                }
            } else {
                synchronized (worker.selectorGuard) {
                    selector.wakeup();
                    interestOps = key.interestOps();
                    if ((interestOps & SelectionKey.OP_WRITE) != 0) {
                        interestOps &= ~SelectionKey.OP_WRITE;
                        key.interestOps(interestOps);
                        changed = true;
                    }
                }
            }
        }

        if (changed) {
            channel.setInterestOpsNow(interestOps);
            fireChannelInterestChanged(channel, interestOps);
        }
    }

    static void close(NioSocketChannel channel, ChannelFuture future) {
        NioWorker worker = channel.getWorker();
        if (worker != null) {
            Selector selector = worker.selector;
            SelectionKey key = channel.socket.keyFor(selector);
            if (key != null) {
                key.cancel();
            }
        }

        boolean connected = channel.isConnected();
        boolean bound = channel.isBound();
        try {
            channel.socket.close();
            future.setSuccess();
            if (channel.setClosed()) {
                if (connected) {
                    if (channel.getInterestOps() != Channel.OP_WRITE) {
                        channel.setInterestOpsNow(Channel.OP_WRITE);
                        fireChannelInterestChanged(channel, Channel.OP_WRITE);
                    }
                    fireChannelDisconnected(channel);
                }
                if (bound) {
                    fireChannelUnbound(channel);
                }
                fireChannelClosed(channel);
            }
        } catch (Throwable t) {
            future.setFailure(t);
            fireExceptionCaught(channel, t);
        }
    }

    static void setInterestOps(
            NioSocketChannel channel, ChannelFuture future, int interestOps) {
        NioWorker worker = channel.getWorker();
        if (worker == null) {
            IllegalStateException cause =
                new IllegalStateException("Channel not connected yet (null worker)");
            future.setFailure(cause);
            fireExceptionCaught(channel, cause);
            return;
        }

        Selector selector = worker.selector;
        SelectionKey key = channel.socket.keyFor(selector);
        if (key == null || selector == null) {
            IllegalStateException cause =
                new IllegalStateException("Channel not connected yet (SelectionKey not found)");
            future.setFailure(cause);
            fireExceptionCaught(channel, cause);
        }

        boolean changed = false;
        try {
            if (Thread.currentThread() == worker.thread) {
                if (key.interestOps() != interestOps) {
                    key.interestOps(interestOps);
                    changed = true;
                }
            } else {
                synchronized (worker.selectorGuard) {
                    selector.wakeup();
                    if (key.interestOps() != interestOps) {
                        key.interestOps(interestOps);
                        changed = true;
                    }
                }
            }

            future.setSuccess();
            if (changed) {
                channel.setInterestOpsNow(interestOps);
                fireChannelInterestChanged(channel, interestOps);
            }
        } catch (Throwable t) {
            future.setFailure(t);
            fireExceptionCaught(channel, t);
        }
    }
}
TOP

Related Classes of net.gleamynode.netty.channel.socket.nio.NioWorker

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.