All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.code.yanf4j.nio.impl.Reactor Maven / Gradle / Ivy

There is a newer version: 2.4.8
Show newest version
/*
 * 
 */

package com.google.code.yanf4j.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.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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.EventType;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.nio.NioSession;
import com.google.code.yanf4j.util.SystemUtils;

/**
 * Reactor pattern
 * 
 * @author dennis
 * 
 */
public final class Reactor extends Thread {
	/**
	 * JVM bug threshold
	 */
	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 DEFAULT_WAIT = 1000;

	private static final Logger log = LoggerFactory.getLogger("remoting");

	private boolean jvmBug0;
	private boolean jvmBug1;

	private final int reactorIndex;

	private final SelectorManager selectorManager;

	private final AtomicInteger jvmBug = new AtomicInteger(0);

	private long lastJVMBug;

	private Selector selector;

	private final NioController controller;

	private final Configuration configuration;

	static public class PaddingAtomicBoolean extends AtomicBoolean {

		/**
		 * 
		 */
		private static final long serialVersionUID = 5227571972657902891L;
		public int p1;
		public long p2, p3, p4, p5, p6, p7, p8;

		PaddingAtomicBoolean(boolean v) {
			super(v);
		}
	}

	private final AtomicBoolean wakenUp = new PaddingAtomicBoolean(false);

	public static class RegisterEvent {
		SelectableChannel channel;
		int ops;
		EventType eventType;
		Object attachment;
		Session session;

		public RegisterEvent(SelectableChannel channel, int ops,
				Object attachment) {
			super();
			this.channel = channel;
			this.ops = ops;
			this.attachment = attachment;
		}

		public RegisterEvent(Session session, EventType eventType) {
			super();
			this.session = session;
			this.eventType = eventType;
		}
	}

	private Queue register;

	private final Lock gate = new ReentrantLock();

	private int selectTries = 0;

	private long nextTimeout = 0;

	Reactor(SelectorManager selectorManager, Configuration configuration,
			int index) throws IOException {
		super();
		reactorIndex = index;
		this.register = (Queue) SystemUtils
				.createTransferQueue();
		this.selectorManager = selectorManager;
		controller = selectorManager.getController();
		selector = SystemUtils.openSelector();
		this.configuration = configuration;
		setName("Xmemcached-Reactor-" + index);
		setDaemon(true);
	}

	public final Selector getSelector() {
		return selector;
	}

	public int getReactorIndex() {
		return reactorIndex;
	}

	@Override
	public void run() {
		selectorManager.notifyReady();
		while (selectorManager.isStarted() && selector.isOpen()) {
			try {
				beforeSelect();
				wakenUp.set(false);
				long before = -1;
				// Wether to look jvm bug
				if (SystemUtils.isLinuxPlatform()
						&& !SystemUtils.isAfterJava6u4Version()) {
					before = System.currentTimeMillis();
				}
				long wait = DEFAULT_WAIT;
				if (nextTimeout > 0) {
					wait = nextTimeout;
				}
				int selected = selector.select(wait);
				if (selected == 0) {
					if (before != -1) {
						lookJVMBug(before, selected, wait);
					}
					selectTries++;
					// check tmeout and idle
					nextTimeout = checkSessionTimeout();
					continue;
				} else {
					selectTries = 0;
				}

			} catch (ClosedSelectorException e) {
				break;
			} catch (IOException e) {
				log.error("Reactor select error", e);
				if (selector.isOpen()) {
					continue;
				} else {
					break;
				}
			}
			Set selectedKeys = selector.selectedKeys();
			gate.lock();
			try {
				postSelect(selectedKeys, selector.keys());
				dispatchEvent(selectedKeys);
			} finally {
				gate.unlock();
			}
		}
		if (selector != null) {
			if (selector.isOpen()) {
				try {
					controller.closeChannel(selector);
					selector.close();
				} catch (IOException e) {
					controller.notifyException(e);
					log.error("stop reactor error", e);
				}
			}
		}

	}

	/**
	 * Look jvm bug
	 * 
	 * @param before
	 * @param selected
	 * @param wait
	 * @return
	 * @throws IOException
	 */
	private boolean lookJVMBug(long before, int selected, long wait)
			throws IOException {
		boolean seeing = false;
		long now = System.currentTimeMillis();

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

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

						final SelectableChannel channel = k.channel();
						final Object attachment = k.attachment();

						channel.register(new_selector, k.interestOps(),
								attachment);
					}

					selector.close();
					selector = new_selector;

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

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

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

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

			} catch (CancelledKeyException e) {
				// ignore
			} catch (RejectedExecutionException e) {

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

	final void unregisterChannel(SelectableChannel channel) throws IOException {
		Selector selector = this.selector;
		if (selector != null) {
			if (channel != null) {
				SelectionKey key = channel.keyFor(selector);
				if (key != null) {
					key.cancel();
				}
			}
		}
		wakeup();
	}

	/**
	 * Check session timeout or idle
	 * 
	 * @return
	 */
	private final long checkSessionTimeout() {
		long nextTimeout = 0;
		if (configuration.getCheckSessionTimeoutInterval() > 0) {
			gate.lock();
			try {
				if (selectTries * 1000 >= configuration
						.getCheckSessionTimeoutInterval()) {
					nextTimeout = configuration
							.getCheckSessionTimeoutInterval();
					for (SelectionKey key : selector.keys()) {

						if (key.attachment() != null) {
							long n = checkExpiredIdle(key,
									getSessionFromAttchment(key));
							nextTimeout = n < nextTimeout ? n : nextTimeout;
						}
					}
					selectTries = 0;
				}
			} finally {
				gate.unlock();
			}
		}
		return nextTimeout;
	}

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

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

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

	final void beforeSelect() {
		controller.checkStatisticsForRestart();
		processRegister();
	}

	private final void processRegister() {
		RegisterEvent event = null;
		while ((event = register.poll()) != null) {
			if (event.session != null) {
				dispatchSessionEvent(event.session, event.eventType);
			} else {
				registerChannelNow(event.channel, event.ops, event.attachment);
			}
		}
	}

	Configuration getConfiguration() {
		return configuration;
	}

	private final void dispatchSessionEvent(Session session, EventType event) {
		if (session.isClosed() && event != EventType.UNREGISTER) {
			return;
		}
		if (EventType.REGISTER.equals(event)) {
			controller.registerSession(session);
		} else if (EventType.UNREGISTER.equals(event)) {
			controller.unregisterSession(session);
		} else {
			((NioSession) session).onEvent(event, selector);
		}
	}

	public final void postSelect(Set selectedKeys,
			Set allKeys) {
		if (controller.getSessionTimeout() > 0
				|| controller.getSessionIdleTimeout() > 0) {
			for (SelectionKey key : allKeys) {

				if (!selectedKeys.contains(key)) {
					if (key.attachment() != null) {
						checkExpiredIdle(key, getSessionFromAttchment(key));
					}
				}
			}
		}
	}

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

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

	private final boolean checkExpired(SelectionKey key, Session session) {
		if (session != null && session.isExpired()) {
			((NioSession) session).onEvent(EventType.EXPIRED, selector);
			controller.closeSelectionKey(key);
			return true;
		}
		return false;
	}

	public final void registerChannel(SelectableChannel channel, int ops,
			Object attachment) {
		if (isReactorThread()) {
			registerChannelNow(channel, ops, attachment);
		} else {
			register.offer(new RegisterEvent(channel, ops, attachment));
			wakeup();
		}

	}

	private void registerChannelNow(SelectableChannel channel, int ops,
			Object attachment) {
		if (channel.isOpen()) {
			gate.lock();
			try {
				channel.register(selector, ops, attachment);

			} catch (ClosedChannelException e) {
				log.error("Register channel error", e);
				controller.notifyException(e);
			} finally {
				gate.unlock();
			}
		}
	}

	final void wakeup() {
		if (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();
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy