com.taobao.gecko.core.nio.impl.Reactor.java Source code

Java tutorial

Introduction

Here is the source code for com.taobao.gecko.core.nio.impl.Reactor.java

Source

/*
 * (C) 2007-2012 Alibaba Group Holding Limited.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * 
 */

package com.taobao.gecko.core.nio.impl;

import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Date;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.taobao.gecko.core.config.Configuration;
import com.taobao.gecko.core.core.EventType;
import com.taobao.gecko.core.core.Session;
import com.taobao.gecko.core.core.impl.AbstractSession;
import com.taobao.gecko.core.nio.NioSession;
import com.taobao.gecko.core.util.LinkedTransferQueue;
import com.taobao.gecko.core.util.SystemUtils;

/**
 * 
 * Reactor
 * 
 * @author boyan
 * 
 * @since 1.0, 2009-12-24 01:25:19
 */

public final class Reactor extends Thread {
    /**
     * 
     * 
     * @author boyan
     * @Date 2010-5-20
     * 
     */
    private final class TimerQueueVisitor implements TimerRefQueue.TimerQueueVisitor {
        private final long now;

        private TimerQueueVisitor(final long now) {
            this.now = now;
        }

        public boolean visit(final TimerRef timerRef) {
            if (!timerRef.isCanceled()) {
                // 
                if (timerRef.getTimeoutTimestamp() < this.now) {
                    Reactor.this.timerQueue.remove(timerRef);
                    Reactor.this.controller.onTimeout(timerRef);
                } else if (this.now - timerRef.addTimestamp >= TIMEOUT_THRESOLD) {
                    // 
                    Reactor.this.timerQueue.remove(timerRef);
                    Reactor.this.timerHeap.offer(timerRef);
                }

            }
            return true;
        }
    }

    public static final long TIMEOUT_THRESOLD = Long
            .parseLong(System.getProperty("notify.remoting.timer.timeout_threshold", "500"));
    /**
     * jvm bug
     */
    public static final int JVMBUG_THRESHHOLD = Integer.getInteger("com.googlecode.yanf4j.nio.JVMBUG_THRESHHOLD",
            128);
    public static final int JVMBUG_THRESHHOLD2 = JVMBUG_THRESHHOLD * 2;
    public static final int JVMBUG_THRESHHOLD1 = (JVMBUG_THRESHHOLD2 + JVMBUG_THRESHHOLD) / 2;

    public static final int MAX_TIMER_COUNT = 500000;

    public static final int MAX_TIME_OUT_EVENT_PER_TIME = 2000;

    private static final Log log = LogFactory.getLog(Reactor.class);

    // bug
    private boolean jvmBug0;
    private boolean jvmBug1;

    private final int reactorIndex;

    private final SelectorManager selectorManager;

    // bug
    private final AtomicInteger jvmBug = new AtomicInteger(0);

    // bug
    private long lastJVMBug;

    private volatile Selector selector;

    private final NioController controller;

    private final Configuration configuration;

    private final AtomicBoolean wakenUp = new AtomicBoolean(false);

    /**
     * 
     */
    private final Queue<Object[]> register = new LinkedTransferQueue<Object[]>();

    private final TimerRefQueue timerQueue = new TimerRefQueue();

    /**
     * cancelkeyAtomicInteger
     */
    private volatile int cancelledKeys;

    // cancel keysselectNow
    static final int CLEANUP_INTERVAL = 256;

    /**
     * 
     */
    private final PriorityQueue<TimerRef> timerHeap = new PriorityQueue<TimerRef>();
    /**
     * 
     */
    private volatile long timeCache;

    private final Lock gate = new ReentrantLock();

    private volatile int selectTries = 0;

    private long nextTimeout = 0;

    private long lastMoveTimestamp = 0; // timerQueuetimerHeap

    Reactor(final SelectorManager selectorManager, final Configuration configuration, final int index)
            throws IOException {
        super();
        this.reactorIndex = index;
        this.selectorManager = selectorManager;
        this.controller = selectorManager.getController();
        this.selector = SystemUtils.openSelector();
        this.configuration = configuration;
        this.setName("notify-remoting-reactor-" + index);
    }

    final Selector getSelector() {
        return this.selector;
    }

    public int getReactorIndex() {
        return this.reactorIndex;
    }

    /**
     * 
     * 
     * @return
     */
    private long timeoutNext() {
        long selectionTimeout = TIMEOUT_THRESOLD;
        TimerRef timerRef = this.timerHeap.peek();
        while (timerRef != null && timerRef.isCanceled()) {
            this.timerHeap.poll();
            timerRef = this.timerHeap.peek();
        }
        if (timerRef != null) {
            final long now = this.getTime();
            // -1select
            if (timerRef.getTimeoutTimestamp() < now) {
                selectionTimeout = -1L;
            } else {
                selectionTimeout = timerRef.getTimeoutTimestamp() - now;
            }
        }
        return selectionTimeout;
    }

    /**
     * Select
     */
    @Override
    public void run() {
        this.selectorManager.notifyReady();
        while (this.selectorManager.isStarted() && this.selector.isOpen()) {
            try {
                this.cancelledKeys = 0;
                this.beforeSelect();

                long before = -1;
                if (this.isNeedLookingJVMBug()) {
                    before = System.currentTimeMillis();
                }
                long wait = this.timeoutNext();
                if (this.nextTimeout > 0 && this.nextTimeout < wait) {
                    wait = this.nextTimeout;
                }
                // 
                this.timeCache = 0;
                this.wakenUp.set(false);
                final int selected = this.select(wait);
                if (selected == 0) {
                    /**
                     * BUGhttp://bugs.sun.com/bugdatabase /view_bug
                     * .do?bug_id=6403933
                     */
                    if (before != -1) {
                        this.lookJVMBug(before, selected, wait);
                    }
                    this.selectTries++;
                    // idletimeout
                    this.nextTimeout = this.checkSessionTimeout();
                } else {
                    this.selectTries = 0;
                }
                // timer
                this.timeCache = this.getTime();
                this.processTimeout();
                this.processSelectedKeys();
            } catch (final ClosedSelectorException e) {
                break;
            } catch (final Exception e) {
                log.error("Reactor select error", e);
                if (this.selector.isOpen()) {
                    continue;
                } else {
                    break;
                }
            }
        }
        if (this.selector != null) {
            if (this.selector.isOpen()) {
                try {
                    this.controller.closeChannel(this.selector);
                    this.selector.selectNow();
                    this.selector.close();
                } catch (final IOException e) {
                    this.controller.notifyException(e);
                    log.error("stop reactor error", e);
                }
            }
        }

    }

    private void processTimeout() {
        if (!this.timerHeap.isEmpty()) {
            final long now = this.getTime();
            TimerRef timerRef = null;
            while ((timerRef = this.timerHeap.peek()) != null) {
                if (timerRef.isCanceled()) {
                    this.timerHeap.poll();
                    continue;
                }
                // break
                if (timerRef.getTimeoutTimestamp() > now) {
                    break;
                }
                // 
                this.controller.onTimeout(this.timerHeap.poll());
            }
        }
    }

    private Set<SelectionKey> processSelectedKeys() throws IOException {
        final Set<SelectionKey> selectedKeys = this.selector.selectedKeys();
        this.gate.lock();
        try {
            this.postSelect(selectedKeys, this.selector.keys());
            this.dispatchEvent(selectedKeys);
        } finally {
            this.gate.unlock();
        }
        this.clearCancelKeys();
        return selectedKeys;
    }

    private void clearCancelKeys() throws IOException {
        if (this.cancelledKeys > CLEANUP_INTERVAL) {
            final Selector selector = this.selector;
            selector.selectNow();
            this.cancelledKeys = 0;
        }
    }

    private int select(final long wait) throws IOException {
        // 
        if (wait > 0 && !this.wakenUp.get()) {
            return this.selector.select(wait);
        } else {
            return this.selector.selectNow();
        }
    }

    public long getTime() {
        final long timeCache = this.timeCache;
        if (timeCache > 0) {
            return timeCache;
        } else {
            return System.currentTimeMillis();
        }
    }

    /**
     * 
     * 
     * @param timeout
     * @param runnable
     */
    public void insertTimer(final TimerRef timerRef) {
        if (timerRef.getTimeout() > 0 && timerRef.getRunnable() != null && !timerRef.isCanceled()) {
            final long now = this.getTime();
            final long timestamp = now + timerRef.getTimeout();
            timerRef.setTimeoutTimestamp(timestamp);
            timerRef.addTimestamp = now;
            this.timerQueue.add(timerRef);
        }
    }

    private boolean lookJVMBug(final long before, final int selected, final long wait) throws IOException {
        boolean seeing = false;
        final long now = System.currentTimeMillis();
        /**
         * Bug,(1)select0 (2)select (3) (4)wakenup
         */
        if (JVMBUG_THRESHHOLD > 0 && selected == 0 && wait > JVMBUG_THRESHHOLD && now - before < wait / 4
                && !this.wakenUp.get() /* waken up */
                && !Thread.currentThread().isInterrupted()/* Interrupted */) {
            this.jvmBug.incrementAndGet();
            // 1selector
            if (this.jvmBug.get() >= JVMBUG_THRESHHOLD2) {
                this.gate.lock();
                try {
                    this.lastJVMBug = now;
                    log.warn("JVM bug occured at " + new Date(this.lastJVMBug)
                            + ",http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933,reactIndex="
                            + this.reactorIndex);
                    if (this.jvmBug1) {
                        log.debug("seeing JVM BUG(s) - recreating selector,reactIndex=" + this.reactorIndex);
                    } else {
                        this.jvmBug1 = true;
                        log.info("seeing JVM BUG(s) - recreating selector,reactIndex=" + this.reactorIndex);
                    }
                    seeing = true;
                    // selector
                    final Selector new_selector = SystemUtils.openSelector();

                    for (final SelectionKey k : this.selector.keys()) {
                        if (!k.isValid() || k.interestOps() == 0) {
                            continue;
                        }

                        final SelectableChannel channel = k.channel();
                        final Object attachment = k.attachment();
                        // interestOps>0channel
                        channel.register(new_selector, k.interestOps(), attachment);
                    }

                    this.selector.close();
                    this.selector = new_selector;

                } finally {
                    this.gate.unlock();
                }
                this.jvmBug.set(0);

            } else if (this.jvmBug.get() == JVMBUG_THRESHHOLD || this.jvmBug.get() == JVMBUG_THRESHHOLD1) {
                // BUG0interestedOps==0key
                if (this.jvmBug0) {
                    log.debug("seeing JVM BUG(s) - cancelling interestOps==0,reactIndex=" + this.reactorIndex);
                } else {
                    this.jvmBug0 = true;
                    log.info("seeing JVM BUG(s) - cancelling interestOps==0,reactIndex=" + this.reactorIndex);
                }
                this.gate.lock();
                seeing = true;
                try {
                    for (final SelectionKey k : this.selector.keys()) {
                        if (k.isValid() && k.interestOps() == 0) {
                            k.cancel();
                        }
                    }
                } finally {
                    this.gate.unlock();
                }
            }
        } else {
            this.jvmBug.set(0);
        }
        return seeing;
    }

    private boolean isNeedLookingJVMBug() {
        return SystemUtils.isLinuxPlatform() && !SystemUtils.isAfterJava6u4Version();
    }

    final void dispatchEvent(final Set<SelectionKey> selectedKeySet) {
        final Iterator<SelectionKey> it = selectedKeySet.iterator();
        boolean skipOpRead = false; // 
        while (it.hasNext()) {
            final SelectionKey key = it.next();
            it.remove();
            if (!key.isValid()) {
                if (key.attachment() != null) {
                    this.controller.closeSelectionKey(key);
                } else {
                    key.cancel();
                }
                continue;
            }
            try {
                if (key.isAcceptable()) {
                    this.controller.onAccept(key);
                    continue;
                }
                if ((key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
                    // Remove write interest
                    key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
                    this.controller.onWrite(key);
                    if (!this.controller.isHandleReadWriteConcurrently()) {
                        skipOpRead = true;
                    }
                }
                if (!skipOpRead && (key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
                    // read
                    key.interestOps(key.interestOps() & ~SelectionKey.OP_READ);
                    // 
                    if (!this.controller.getStatistics().isReceiveOverFlow()) {
                        // Remove read interest

                        this.controller.onRead(key);// 
                        continue;
                    } else {
                        key.interestOps(key.interestOps() // 
                                | SelectionKey.OP_READ);
                    }

                }
                if ((key.readyOps() & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT) {
                    this.controller.onConnect(key);
                    continue;
                }

            } catch (final RejectedExecutionException e) {
                // 
                if (key.attachment() instanceof AbstractNioSession) {
                    ((AbstractSession) key.attachment()).onException(e);
                }
                this.controller.notifyException(e);
                if (this.selector.isOpen()) {
                    continue;
                } else {
                    break;
                }
            } catch (final CancelledKeyException e) {
                // ignore
            } catch (final Exception e) {
                if (key.attachment() instanceof AbstractNioSession) {
                    ((AbstractSession) key.attachment()).onException(e);
                }
                this.controller.closeSelectionKey(key);
                this.controller.notifyException(e);
                log.error("Reactor dispatch events error", e);
                if (this.selector.isOpen()) {
                    continue;
                } else {
                    break;
                }
            }
        }
    }

    final void unregisterChannel(final SelectableChannel channel) {
        try {
            final Selector selector = this.selector;
            if (selector != null) {
                if (channel != null) {
                    final SelectionKey key = channel.keyFor(selector);
                    if (key != null) {
                        key.cancel();
                        this.cancelledKeys++;
                    }
                }
            }
            if (channel != null && channel.isOpen()) {
                channel.close();
            }
        } catch (final Throwable t) {
            // ignore
        }
        this.wakeup();
    }

    private final long checkSessionTimeout() {
        long nextTimeout = 0;
        if (this.configuration.getCheckSessionTimeoutInterval() > 0) {
            this.gate.lock();
            try {
                if (this.selectTries * 1000 >= this.configuration.getCheckSessionTimeoutInterval()) {
                    nextTimeout = this.configuration.getCheckSessionTimeoutInterval();
                    for (final SelectionKey key : this.selector.keys()) {
                        // expiredidle
                        if (key.attachment() != null) {
                            final long n = this.checkExpiredIdle(key, this.getSessionFromAttchment(key));
                            nextTimeout = n < nextTimeout ? n : nextTimeout;
                        }
                    }
                    this.selectTries = 0;
                }
            } finally {
                this.gate.unlock();
            }
        }
        return nextTimeout;
    }

    private final Session getSessionFromAttchment(final SelectionKey key) {
        if (key.attachment() instanceof Session) {
            return (Session) key.attachment();
        }
        return null;
    }

    final void registerSession(final Session session, final EventType event) {
        final Selector selector = this.selector;
        if (this.isReactorThread() && selector != null) {
            this.dispatchSessionEvent(session, event, selector);
        } else {
            this.register.offer(new Object[] { session, event });
            this.wakeup();
        }
    }

    private final boolean isReactorThread() {
        return Thread.currentThread() == this;
    }

    final void beforeSelect() throws IOException {
        this.controller.checkStatisticsForRestart();
        this.processRegister();
        this.processMoveTimer();
        this.clearCancelKeys();
    }

    private void processMoveTimer() {
        final long now = this.getTime();
        // 1
        if (now - this.lastMoveTimestamp >= TIMEOUT_THRESOLD && !this.timerQueue.isEmpty()) {
            this.lastMoveTimestamp = now;
            this.timerQueue.iterateQueue(new TimerQueueVisitor(now));
        }
    }

    private final void processRegister() {
        Object[] object = null;
        while ((object = this.register.poll()) != null) {
            switch (object.length) {
            case 2:
                this.dispatchSessionEvent((Session) object[0], (EventType) object[1], this.selector);
                break;
            case 3:
                this.registerChannelNow((SelectableChannel) object[0], (Integer) object[1], object[2],
                        this.selector);
                break;
            }
        }
    }

    Configuration getConfiguration() {
        return this.configuration;
    }

    private final void dispatchSessionEvent(final Session session, final EventType event, final Selector selector) {
        if (EventType.REGISTER.equals(event)) {
            this.controller.registerSession(session);
        } else if (EventType.UNREGISTER.equals(event)) {
            this.controller.unregisterSession(session);
            this.unregisterChannel(((NioSession) session).channel());
        } else {
            ((NioSession) session).onEvent(event, selector);
        }
    }

    final void postSelect(final Set<SelectionKey> selectedKeys, final Set<SelectionKey> allKeys) {
        if (this.controller.getSessionTimeout() > 0 || this.controller.getSessionIdleTimeout() > 0) {
            for (final SelectionKey key : allKeys) {
                // keyidle
                if (!selectedKeys.contains(key)) {
                    if (key.attachment() != null) {
                        this.checkExpiredIdle(key, this.getSessionFromAttchment(key));
                    }
                }
            }
        }
    }

    private long checkExpiredIdle(final SelectionKey key, final Session session) {
        if (session == null) {
            return 0;
        }
        long nextTimeout = 0;
        boolean expired = false;
        if (this.controller.getSessionTimeout() > 0) {
            expired = this.checkExpired(key, session);
            nextTimeout = this.controller.getSessionTimeout();
        }
        if (this.controller.getSessionIdleTimeout() > 0 && !expired) {
            this.checkIdle(session);
            nextTimeout = this.controller.getSessionIdleTimeout();
        }
        return nextTimeout;
    }

    private final void checkIdle(final Session session) {
        if (this.controller.getSessionIdleTimeout() > 0) {
            if (session.isIdle()) {
                ((NioSession) session).onEvent(EventType.IDLE, this.selector);
            }
        }
    }

    private final boolean checkExpired(final SelectionKey key, final Session session) {
        if (session.isExpired()) {
            ((NioSession) session).onEvent(EventType.EXPIRED, this.selector);
            return true;
        }
        return false;
    }

    final void registerChannel(final SelectableChannel channel, final int ops, final Object attachment) {
        final Selector selector = this.selector;
        if (this.isReactorThread() && selector != null) {
            this.registerChannelNow(channel, ops, attachment, selector);
        } else {
            this.register.offer(new Object[] { channel, ops, attachment });
            this.wakeup();
        }

    }

    private void registerChannelNow(final SelectableChannel channel, final int ops, final Object attachment,
            final Selector selector) {
        this.gate.lock();
        try {
            if (channel.isOpen()) {
                channel.register(selector, ops, attachment);
            }
        } catch (final ClosedChannelException e) {
            log.error("Register channel error", e);
            this.controller.notifyException(e);
        } finally {
            this.gate.unlock();
        }
    }

    final void wakeup() {
        if (this.wakenUp.compareAndSet(false, true)) {
            final Selector selector = this.selector;
            if (selector != null) {
                selector.wakeup();
            }
        }
    }

    final void selectNow() throws IOException {
        final Selector selector = this.selector;
        if (selector != null) {
            selector.selectNow();
        }
    }
}