/* * JBoss, Home of Professional Open Source * * Copyright 2012 Red Hat, Inc. and/or its affiliates. * * 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 org.xnio.nativeimpl; import java.io.Closeable; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.locks.LockSupport; import org.xnio.Bits; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.ClosedWorkerException; import org.xnio.IoUtils; import org.xnio.LocalSocketAddress; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.XnioWorker; import org.xnio.channels.AcceptingChannel; import org.xnio.channels.MulticastMessageChannel; import org.xnio.management.XnioWorkerMXBean; import static java.util.concurrent.locks.LockSupport.unpark; import static org.xnio.nativeimpl.Log.log; /** * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ final class NativeXnioWorker extends XnioWorker { private static final int CLOSE_REQ = (1 << 31); private static final int CLOSE_COMP = (1 << 30); // start at 1 for the provided thread pool private volatile int state = 1; private final NativeWorkerThread[] workerThreads; private final Closeable mbeanHandle; @SuppressWarnings("unused") private volatile Thread shutdownWaiter; private static final AtomicReferenceFieldUpdater<NativeXnioWorker, Thread> shutdownWaiterUpdater = AtomicReferenceFieldUpdater.newUpdater(NativeXnioWorker.class, Thread.class, "shutdownWaiter"); private static final AtomicIntegerFieldUpdater<NativeXnioWorker> stateUpdater = AtomicIntegerFieldUpdater.newUpdater(NativeXnioWorker.class, "state"); @SuppressWarnings("deprecation") NativeXnioWorker(final NativeXnio xnio, final ThreadGroup threadGroup, final OptionMap optionMap, final Runnable terminationTask) throws IOException { super(xnio, threadGroup, optionMap, terminationTask); final int threadCount; if (optionMap.contains(Options.WORKER_IO_THREADS)) { threadCount = optionMap.get(Options.WORKER_IO_THREADS, 0); } else { threadCount = Math.max(optionMap.get(Options.WORKER_READ_THREADS, 1), optionMap.get(Options.WORKER_WRITE_THREADS, 1)); } if (threadCount < 0) { throw log.optionOutOfRange("WORKER_IO_THREADS"); } final long workerStackSize = optionMap.get(Options.STACK_SIZE, 0L); if (workerStackSize < 0L) { throw log.optionOutOfRange("STACK_SIZE"); } final String workerName = getName(); final NativeWorkerThread[] workerThreads; workerThreads = new NativeWorkerThread[threadCount]; final boolean markWorkerThreadAsDaemon = optionMap.get(Options.THREAD_DAEMON, false); boolean ok = false; try { for (int i = 0; i < threadCount; i++) { final NativeWorkerThread readWorker; if (Native.HAS_EPOLL) { readWorker = new EPollWorkerThread(this, i, String.format("%s I/O-%d", workerName, Integer.valueOf(i + 1)), threadGroup, workerStackSize); } else { throw new IOException("No suitable worker implementations available"); } // Mark as daemon if the Options.THREAD_DAEMON has been set if (markWorkerThreadAsDaemon) { readWorker.setDaemon(true); } workerThreads[i] = readWorker; } ok = true; } finally { if (! ok) { for (NativeWorkerThread worker : workerThreads) { if (worker != null) { worker.close(); } } } } this.workerThreads = workerThreads; mbeanHandle = NativeXnio.register(new XnioWorkerMXBean() { public String getProviderName() { return "native"; } public String getName() { return workerName; } public boolean isShutdownRequested() { return isShutdown(); } public int getCoreWorkerPoolSize() { return NativeXnioWorker.this.getCoreWorkerPoolSize(); } public int getMaxWorkerPoolSize() { return NativeXnioWorker.this.getMaxWorkerPoolSize(); } public int getIoThreadCount() { return workerThreads.length; } }); } void start() { for (NativeWorkerThread worker : workerThreads) { openResourceUnconditionally(); worker.start(); } } protected NativeWorkerThread chooseThread() { final NativeWorkerThread[] workers = this.workerThreads; final int length = workers.length; if (length == 0) { throw log.noThreads(); } if (length == 1) { return workers[0]; } final Random random = IoUtils.getThreadLocalRandom(); return workers[random.nextInt(length)]; } public int getIoThreadCount() { return workerThreads.length; } NativeWorkerThread[] getAll() { return workerThreads; } protected AcceptingChannel<StreamConnection> createTcpConnectionServer(final InetSocketAddress bindAddress, final ChannelListener<? super AcceptingChannel<StreamConnection>> acceptListener, final OptionMap optionMap) throws IOException { checkShutdown(); final InetAddress address = bindAddress.getAddress(); final int fd; if (address instanceof Inet4Address) { fd = Native.socketTcp(null); } else if (address instanceof Inet6Address) { fd = Native.socketTcp6(null); } else { throw new IllegalArgumentException("Unknown address format"); } Native.testAndThrow(fd); Native.testAndThrow(Native.setOptReuseAddr(fd, optionMap.get(Options.REUSE_ADDRESSES, true), null)); boolean ok = false; try { Native.testAndThrow(Native.bind(fd, Native.encodeSocketAddress(bindAddress), null)); final TcpServer server = new TcpServer(this, fd, optionMap); Native.testAndThrow(Native.listen(fd, optionMap.get(Options.BACKLOG, 128), null)); server.setAcceptListener(acceptListener); server.register(); ok = true; return server; } finally { if (! ok) { Native.close(fd, null); } } } protected AcceptingChannel<StreamConnection> createLocalStreamConnectionServer(final LocalSocketAddress bindAddress, final ChannelListener<? super AcceptingChannel<StreamConnection>> acceptListener, final OptionMap optionMap) throws IOException { checkShutdown(); final int fd = Native.socketLocalStream(null); Native.testAndThrow(fd); boolean ok = false; try { Native.testAndThrow(Native.bind(fd, Native.encodeSocketAddress(bindAddress), null)); final UnixServer server = new UnixServer(this, fd, optionMap); Native.testAndThrow(Native.listen(fd, optionMap.get(Options.BACKLOG, 128), null)); server.setAcceptListener(acceptListener); server.register(); ok = true; return server; } finally { if (! ok) { Native.close(fd, null); } } } /** {@inheritDoc} */ public MulticastMessageChannel createUdpServer(final InetSocketAddress bindAddress, final ChannelListener<? super MulticastMessageChannel> bindListener, final OptionMap optionMap) throws IOException { if (true) throw new IOException("Not implemented yet"); checkShutdown(); final InetAddress address = bindAddress.getAddress(); final int fd; if (address instanceof Inet4Address) { fd = Native.testAndThrow(Native.socketUdp(null)); } else if (address instanceof Inet6Address) { fd = Native.testAndThrow(Native.socketUdp6(null)); } else { throw new IllegalArgumentException("Unknown address format"); } boolean ok = false; try { // if (optionMap.contains(Options.BROADCAST)) // if (optionMap.contains(Options.IP_TRAFFIC_CLASS)) // if (optionMap.contains(Options.RECEIVE_BUFFER)) // channel.socket().setReuseAddress(optionMap.get(Options.REUSE_ADDRESSES, true)); // if (optionMap.contains(Options.SEND_BUFFER)) Native.testAndThrow(Native.bind(fd, Native.encodeSocketAddress(bindAddress), null)); ChannelListeners.invokeChannelListener(null, bindListener); return null; } finally { if (! ok) { Native.close(fd, null); } } } public boolean isShutdown() { return (state & CLOSE_REQ) != 0; } public boolean isTerminated() { return (state & CLOSE_COMP) != 0; } /** * Open a resource unconditionally (i.e. accepting a connection on an open server). */ void openResourceUnconditionally() { int oldState = stateUpdater.getAndIncrement(this); if (log.isTraceEnabled()) { log.tracef("CAS %s %08x -> %08x", this, Integer.valueOf(oldState), Integer.valueOf(oldState + 1)); } } void checkShutdown() throws ClosedWorkerException { if (isShutdown()) throw log.workerShutDown(); } void closeResource() { int oldState = stateUpdater.decrementAndGet(this); if (log.isTraceEnabled()) { log.tracef("CAS %s %08x -> %08x", this, Integer.valueOf(oldState + 1), Integer.valueOf(oldState)); } while (oldState == CLOSE_REQ) { if (stateUpdater.compareAndSet(this, CLOSE_REQ, CLOSE_REQ | CLOSE_COMP)) { log.tracef("CAS %s %08x -> %08x (close complete)", this, Integer.valueOf(CLOSE_REQ), Integer.valueOf(CLOSE_REQ | CLOSE_COMP)); safeUnpark(shutdownWaiterUpdater.getAndSet(this, null)); final Runnable task = getTerminationTask(); if (task != null) try { task.run(); } catch (Throwable ignored) {} return; } oldState = state; } } public void shutdown() { int oldState; oldState = state; while ((oldState & CLOSE_REQ) == 0) { // need to do the close ourselves... if (! stateUpdater.compareAndSet(this, oldState, oldState | CLOSE_REQ)) { // changed in the meantime oldState = state; continue; } log.tracef("Initiating shutdown of %s", this); for (NativeWorkerThread worker : workerThreads) { worker.shutdown(); } shutDownTaskPool(); return; } log.tracef("Idempotent shutdown of %s", this); } public List<Runnable> shutdownNow() { shutdown(); return shutDownTaskPoolNow(); } public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException { int oldState = state; if (Bits.allAreSet(oldState, CLOSE_COMP)) { return true; } long then = System.nanoTime(); long duration = unit.toNanos(timeout); final Thread myThread = Thread.currentThread(); while (Bits.allAreClear(oldState = state, CLOSE_COMP)) { final Thread oldThread = shutdownWaiterUpdater.getAndSet(this, myThread); try { if (Bits.allAreSet(oldState = state, CLOSE_COMP)) { break; } LockSupport.parkNanos(this, duration); if (Thread.interrupted()) { throw new InterruptedException(); } long now = System.nanoTime(); duration -= now - then; if (duration < 0L) { oldState = state; break; } } finally { safeUnpark(oldThread); } } return Bits.allAreSet(oldState, CLOSE_COMP); } public void awaitTermination() throws InterruptedException { int oldState = state; if (Bits.allAreSet(oldState, CLOSE_COMP)) { return; } final Thread myThread = Thread.currentThread(); while (Bits.allAreClear(state, CLOSE_COMP)) { final Thread oldThread = shutdownWaiterUpdater.getAndSet(this, myThread); try { if (Bits.allAreSet(state, CLOSE_COMP)) { break; } LockSupport.park(this); if (Thread.interrupted()) { throw new InterruptedException(); } } finally { safeUnpark(oldThread); } } } private static void safeUnpark(final Thread waiter) { if (waiter != null) unpark(waiter); } protected void taskPoolTerminated() { closeResource(); } public NativeXnio getXnio() { return (NativeXnio) super.getXnio(); } }