// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.channel.nio;

import io.netty.channel.IoEvent;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.channel.SelectStrategyFactory;
import io.netty.channel.DefaultSelectStrategyFactory;
import io.netty.channel.IoHandlerFactory;
import java.util.concurrent.TimeUnit;
import java.util.Collection;
import java.util.ArrayList;
import io.netty.channel.IoHandlerContext;
import java.nio.channels.CancelledKeyException;
import io.netty.channel.IoRegistration;
import io.netty.channel.IoOps;
import io.netty.util.internal.StringUtil;
import io.netty.channel.IoHandle;
import java.util.Iterator;
import java.nio.channels.SelectionKey;
import java.util.Set;
import java.lang.reflect.Field;
import java.lang.reflect.AccessibleObject;
import io.netty.util.internal.ReflectionUtil;
import java.security.AccessController;
import io.netty.util.internal.PlatformDependent;
import java.security.PrivilegedAction;
import java.io.IOException;
import io.netty.channel.ChannelException;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.concurrent.ThreadAwareExecutor;
import io.netty.channel.SelectStrategy;
import java.util.concurrent.atomic.AtomicBoolean;
import java.nio.channels.spi.SelectorProvider;
import java.nio.channels.Selector;
import io.netty.util.IntSupplier;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.channel.IoHandler;

public final class NioIoHandler implements IoHandler
{
    private static final InternalLogger logger;
    private static final int CLEANUP_INTERVAL = 256;
    private static final boolean DISABLE_KEY_SET_OPTIMIZATION;
    private static final int MIN_PREMATURE_SELECTOR_RETURNS = 3;
    private static final int SELECTOR_AUTO_REBUILD_THRESHOLD;
    private final IntSupplier selectNowSupplier;
    private Selector selector;
    private Selector unwrappedSelector;
    private SelectedSelectionKeySet selectedKeys;
    private final SelectorProvider provider;
    private final AtomicBoolean wakenUp;
    private final SelectStrategy selectStrategy;
    private final ThreadAwareExecutor executor;
    private int cancelledKeys;
    private boolean needsToSelectAgain;
    
    private NioIoHandler(final ThreadAwareExecutor executor, final SelectorProvider selectorProvider, final SelectStrategy strategy) {
        this.selectNowSupplier = new IntSupplier() {
            @Override
            public int get() throws Exception {
                return NioIoHandler.this.selectNow();
            }
        };
        this.wakenUp = new AtomicBoolean();
        this.executor = ObjectUtil.checkNotNull(executor, "executionContext");
        this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
        this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
        final SelectorTuple selectorTuple = this.openSelector();
        this.selector = selectorTuple.selector;
        this.unwrappedSelector = selectorTuple.unwrappedSelector;
    }
    
    private SelectorTuple openSelector() {
        Selector unwrappedSelector;
        try {
            unwrappedSelector = this.provider.openSelector();
        }
        catch (final IOException e) {
            throw new ChannelException("failed to open a new selector", e);
        }
        if (NioIoHandler.DISABLE_KEY_SET_OPTIMIZATION) {
            return new SelectorTuple(unwrappedSelector);
        }
        final Object maybeSelectorImplClass = AccessController.doPrivileged((PrivilegedAction<Object>)new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                try {
                    return Class.forName("sun.nio.ch.SelectorImpl", false, PlatformDependent.getSystemClassLoader());
                }
                catch (final Throwable cause) {
                    return cause;
                }
            }
        });
        if (!(maybeSelectorImplClass instanceof Class) || !((Class)maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
            if (maybeSelectorImplClass instanceof Throwable) {
                final Throwable t = (Throwable)maybeSelectorImplClass;
                NioIoHandler.logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
            }
            return new SelectorTuple(unwrappedSelector);
        }
        final Class<?> selectorImplClass = (Class<?>)maybeSelectorImplClass;
        final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
        final Object maybeException = AccessController.doPrivileged((PrivilegedAction<Object>)new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                try {
                    final Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
                    final Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
                    if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {
                        final long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);
                        final long publicSelectedKeysFieldOffset = PlatformDependent.objectFieldOffset(publicSelectedKeysField);
                        if (selectedKeysFieldOffset != -1L && publicSelectedKeysFieldOffset != -1L) {
                            PlatformDependent.putObject(unwrappedSelector, selectedKeysFieldOffset, selectedKeySet);
                            PlatformDependent.putObject(unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet);
                            return null;
                        }
                    }
                    Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
                    if (cause != null) {
                        return cause;
                    }
                    cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
                    if (cause != null) {
                        return cause;
                    }
                    selectedKeysField.set(unwrappedSelector, selectedKeySet);
                    publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
                    return null;
                }
                catch (final NoSuchFieldException | IllegalAccessException e) {
                    return e;
                }
            }
        });
        if (maybeException instanceof Exception) {
            this.selectedKeys = null;
            final Exception e2 = (Exception)maybeException;
            NioIoHandler.logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e2);
            return new SelectorTuple(unwrappedSelector);
        }
        this.selectedKeys = selectedKeySet;
        NioIoHandler.logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
        return new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
    }
    
    public SelectorProvider selectorProvider() {
        return this.provider;
    }
    
    Selector selector() {
        return this.selector;
    }
    
    int numRegistered() {
        return this.selector().keys().size() - this.cancelledKeys;
    }
    
    Set<SelectionKey> registeredSet() {
        return this.selector().keys();
    }
    
    void rebuildSelector0() {
        final Selector oldSelector = this.selector;
        if (oldSelector == null) {
            return;
        }
        SelectorTuple newSelectorTuple;
        try {
            newSelectorTuple = this.openSelector();
        }
        catch (final Exception e) {
            NioIoHandler.logger.warn("Failed to create a new Selector.", e);
            return;
        }
        int nChannels = 0;
        for (final SelectionKey key : oldSelector.keys()) {
            final DefaultNioRegistration handle = (DefaultNioRegistration)key.attachment();
            try {
                if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
                    continue;
                }
                handle.register(newSelectorTuple.unwrappedSelector);
                ++nChannels;
            }
            catch (final Exception e2) {
                NioIoHandler.logger.warn("Failed to re-register a NioHandle to the new Selector.", e2);
                handle.cancel();
            }
        }
        this.selector = newSelectorTuple.selector;
        this.unwrappedSelector = newSelectorTuple.unwrappedSelector;
        try {
            oldSelector.close();
        }
        catch (final Throwable t) {
            if (NioIoHandler.logger.isWarnEnabled()) {
                NioIoHandler.logger.warn("Failed to close the old Selector.", t);
            }
        }
        if (NioIoHandler.logger.isInfoEnabled()) {
            NioIoHandler.logger.info("Migrated " + nChannels + " channel(s) to the new Selector.");
        }
    }
    
    private static NioIoHandle nioHandle(final IoHandle handle) {
        if (handle instanceof NioIoHandle) {
            return (NioIoHandle)handle;
        }
        throw new IllegalArgumentException("IoHandle of type " + StringUtil.simpleClassName(handle) + " not supported");
    }
    
    private static NioIoOps cast(final IoOps ops) {
        if (ops instanceof NioIoOps) {
            return (NioIoOps)ops;
        }
        throw new IllegalArgumentException("IoOps of type " + StringUtil.simpleClassName(ops) + " not supported");
    }
    
    @Override
    public IoRegistration register(final IoHandle handle) throws Exception {
        final NioIoHandle nioHandle = nioHandle(handle);
        final NioIoOps ops = NioIoOps.NONE;
        boolean selected = false;
        while (true) {
            try {
                final IoRegistration registration = new DefaultNioRegistration(this.executor, nioHandle, ops, this.unwrappedSelector());
                handle.registered();
                return registration;
            }
            catch (final CancelledKeyException e) {
                if (!selected) {
                    this.selectNow();
                    selected = true;
                    continue;
                }
                throw e;
            }
            break;
        }
    }
    
    @Override
    public int run(final IoHandlerContext context) {
        int handled = 0;
        try {
            try {
                switch (this.selectStrategy.calculateStrategy(this.selectNowSupplier, !context.canBlock())) {
                    case -2: {
                        if (context.shouldReportActiveIoTime()) {
                            context.reportActiveIoTime(0L);
                        }
                        return 0;
                    }
                    case -3:
                    case -1: {
                        this.select(context, this.wakenUp.getAndSet(false));
                        if (this.wakenUp.get()) {
                            this.selector.wakeup();
                            break;
                        }
                        break;
                    }
                }
            }
            catch (final IOException e) {
                this.rebuildSelector0();
                handleLoopException(e);
                return 0;
            }
            this.cancelledKeys = 0;
            this.needsToSelectAgain = false;
            if (context.shouldReportActiveIoTime()) {
                final long activeIoStartTimeNanos = System.nanoTime();
                handled = this.processSelectedKeys();
                final long activeIoEndTimeNanos = System.nanoTime();
                context.reportActiveIoTime(activeIoEndTimeNanos - activeIoStartTimeNanos);
            }
            else {
                handled = this.processSelectedKeys();
            }
        }
        catch (final Error e2) {
            throw e2;
        }
        catch (final Throwable t) {
            handleLoopException(t);
        }
        return handled;
    }
    
    private static void handleLoopException(final Throwable t) {
        NioIoHandler.logger.warn("Unexpected exception in the selector loop.", t);
        try {
            Thread.sleep(1000L);
        }
        catch (final InterruptedException ex) {}
    }
    
    private int processSelectedKeys() {
        if (this.selectedKeys != null) {
            return this.processSelectedKeysOptimized();
        }
        return this.processSelectedKeysPlain(this.selector.selectedKeys());
    }
    
    @Override
    public void destroy() {
        try {
            this.selector.close();
        }
        catch (final IOException e) {
            NioIoHandler.logger.warn("Failed to close a selector.", e);
        }
    }
    
    private int processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
        if (selectedKeys.isEmpty()) {
            return 0;
        }
        Iterator<SelectionKey> i = selectedKeys.iterator();
        int handled = 0;
        while (true) {
            final SelectionKey k = i.next();
            i.remove();
            this.processSelectedKey(k);
            ++handled;
            if (!i.hasNext()) {
                break;
            }
            if (!this.needsToSelectAgain) {
                continue;
            }
            this.selectAgain();
            selectedKeys = this.selector.selectedKeys();
            if (selectedKeys.isEmpty()) {
                break;
            }
            i = selectedKeys.iterator();
        }
        return handled;
    }
    
    private int processSelectedKeysOptimized() {
        int handled = 0;
        for (int i = 0; i < this.selectedKeys.size; ++i) {
            final SelectionKey k = this.selectedKeys.keys[i];
            this.selectedKeys.keys[i] = null;
            this.processSelectedKey(k);
            ++handled;
            if (this.needsToSelectAgain) {
                this.selectedKeys.reset(i + 1);
                this.selectAgain();
                i = -1;
            }
        }
        return handled;
    }
    
    private void processSelectedKey(final SelectionKey k) {
        final DefaultNioRegistration registration = (DefaultNioRegistration)k.attachment();
        if (!registration.isValid()) {
            try {
                registration.handle.close();
            }
            catch (final Exception e) {
                NioIoHandler.logger.debug("Exception during closing " + registration.handle, e);
            }
            return;
        }
        registration.handle(k.readyOps());
    }
    
    @Override
    public void prepareToDestroy() {
        this.selectAgain();
        final Set<SelectionKey> keys = this.selector.keys();
        final Collection<DefaultNioRegistration> registrations = new ArrayList<DefaultNioRegistration>(keys.size());
        for (final SelectionKey k : keys) {
            final DefaultNioRegistration handle = (DefaultNioRegistration)k.attachment();
            registrations.add(handle);
        }
        for (final DefaultNioRegistration reg : registrations) {
            reg.close();
        }
    }
    
    @Override
    public void wakeup() {
        if (!this.executor.isExecutorThread(Thread.currentThread()) && this.wakenUp.compareAndSet(false, true)) {
            this.selector.wakeup();
        }
    }
    
    @Override
    public boolean isCompatible(final Class<? extends IoHandle> handleType) {
        return NioIoHandle.class.isAssignableFrom(handleType);
    }
    
    Selector unwrappedSelector() {
        return this.unwrappedSelector;
    }
    
    private void select(final IoHandlerContext runner, final boolean oldWakenUp) throws IOException {
        Selector selector = this.selector;
        try {
            int selectCnt = 0;
            long currentTimeNanos = System.nanoTime();
            final long delayNanos = runner.delayNanos(currentTimeNanos);
            long selectDeadLineNanos = Long.MAX_VALUE;
            if (delayNanos != Long.MAX_VALUE) {
                selectDeadLineNanos = currentTimeNanos + runner.delayNanos(currentTimeNanos);
            }
            while (true) {
                long timeoutMillis;
                if (delayNanos != Long.MAX_VALUE) {
                    final long millisBeforeDeadline = millisBeforeDeadline(selectDeadLineNanos, currentTimeNanos);
                    if (millisBeforeDeadline <= 0L) {
                        if (selectCnt == 0) {
                            selector.selectNow();
                            selectCnt = 1;
                            break;
                        }
                        break;
                    }
                    else {
                        timeoutMillis = millisBeforeDeadline;
                    }
                }
                else {
                    timeoutMillis = 0L;
                }
                if (!runner.canBlock() && this.wakenUp.compareAndSet(false, true)) {
                    selector.selectNow();
                    selectCnt = 1;
                    break;
                }
                final int selectedKeys = selector.select(timeoutMillis);
                ++selectCnt;
                if (selectedKeys != 0 || oldWakenUp || this.wakenUp.get()) {
                    break;
                }
                if (!runner.canBlock()) {
                    break;
                }
                if (Thread.interrupted()) {
                    if (NioIoHandler.logger.isDebugEnabled()) {
                        NioIoHandler.logger.debug("Selector.select() returned prematurely because Thread.currentThread().interrupt() was called. Use NioHandler.shutdownGracefully() to shutdown the NioHandler.");
                    }
                    selectCnt = 1;
                    break;
                }
                final long time = System.nanoTime();
                if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                    selectCnt = 1;
                }
                else if (NioIoHandler.SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= NioIoHandler.SELECTOR_AUTO_REBUILD_THRESHOLD) {
                    selector = this.selectRebuildSelector(selectCnt);
                    selectCnt = 1;
                    break;
                }
                currentTimeNanos = time;
            }
            if (selectCnt > 3 && NioIoHandler.logger.isDebugEnabled()) {
                NioIoHandler.logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.", (Object)(selectCnt - 1), selector);
            }
        }
        catch (final CancelledKeyException e) {
            if (NioIoHandler.logger.isDebugEnabled()) {
                NioIoHandler.logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?", selector, e);
            }
        }
    }
    
    private static long millisBeforeDeadline(final long selectDeadLineNanos, final long currentTimeNanos) {
        assert selectDeadLineNanos != Long.MAX_VALUE;
        final long nanosBeforeDeadline = selectDeadLineNanos - currentTimeNanos;
        if (nanosBeforeDeadline >= 9223372036854275807L) {
            return 9223372036854L;
        }
        return (nanosBeforeDeadline + 500000L) / 1000000L;
    }
    
    int selectNow() throws IOException {
        try {
            return this.selector.selectNow();
        }
        finally {
            if (this.wakenUp.get()) {
                this.selector.wakeup();
            }
        }
    }
    
    private Selector selectRebuildSelector(final int selectCnt) throws IOException {
        NioIoHandler.logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.", (Object)selectCnt, this.selector);
        this.rebuildSelector0();
        final Selector selector = this.selector;
        selector.selectNow();
        return selector;
    }
    
    private void selectAgain() {
        this.needsToSelectAgain = false;
        try {
            this.selector.selectNow();
        }
        catch (final Throwable t) {
            NioIoHandler.logger.warn("Failed to update SelectionKeys.", t);
        }
    }
    
    public static IoHandlerFactory newFactory() {
        return newFactory(SelectorProvider.provider(), DefaultSelectStrategyFactory.INSTANCE);
    }
    
    public static IoHandlerFactory newFactory(final SelectorProvider selectorProvider) {
        return newFactory(selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
    }
    
    public static IoHandlerFactory newFactory(final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory) {
        ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
        ObjectUtil.checkNotNull(selectStrategyFactory, "selectStrategyFactory");
        return new IoHandlerFactory() {
            @Override
            public IoHandler newHandler(final ThreadAwareExecutor executor) {
                return new NioIoHandler(executor, selectorProvider, selectStrategyFactory.newSelectStrategy(), null);
            }
            
            @Override
            public boolean isChangingThreadSupported() {
                return true;
            }
        };
    }
    
    static {
        logger = InternalLoggerFactory.getInstance(NioIoHandler.class);
        DISABLE_KEY_SET_OPTIMIZATION = SystemPropertyUtil.getBoolean("io.netty.noKeySetOptimization", false);
        int selectorAutoRebuildThreshold = SystemPropertyUtil.getInt("io.netty.selectorAutoRebuildThreshold", 512);
        if (selectorAutoRebuildThreshold < 3) {
            selectorAutoRebuildThreshold = 0;
        }
        SELECTOR_AUTO_REBUILD_THRESHOLD = selectorAutoRebuildThreshold;
        if (NioIoHandler.logger.isDebugEnabled()) {
            NioIoHandler.logger.debug("-Dio.netty.noKeySetOptimization: {}", (Object)NioIoHandler.DISABLE_KEY_SET_OPTIMIZATION);
            NioIoHandler.logger.debug("-Dio.netty.selectorAutoRebuildThreshold: {}", (Object)NioIoHandler.SELECTOR_AUTO_REBUILD_THRESHOLD);
        }
    }
    
    private static final class SelectorTuple
    {
        final Selector unwrappedSelector;
        final Selector selector;
        
        SelectorTuple(final Selector unwrappedSelector) {
            this.unwrappedSelector = unwrappedSelector;
            this.selector = unwrappedSelector;
        }
        
        SelectorTuple(final Selector unwrappedSelector, final Selector selector) {
            this.unwrappedSelector = unwrappedSelector;
            this.selector = selector;
        }
    }
    
    final class DefaultNioRegistration implements IoRegistration
    {
        private final AtomicBoolean canceled;
        private final NioIoHandle handle;
        private volatile SelectionKey key;
        
        DefaultNioRegistration(final ThreadAwareExecutor executor, final NioIoHandle handle, final NioIoOps initialOps, final Selector selector) throws IOException {
            this.canceled = new AtomicBoolean();
            this.handle = handle;
            this.key = handle.selectableChannel().register(selector, initialOps.value, this);
        }
        
        NioIoHandle handle() {
            return this.handle;
        }
        
        void register(final Selector selector) throws IOException {
            final SelectionKey newKey = this.handle.selectableChannel().register(selector, this.key.interestOps(), this);
            this.key.cancel();
            this.key = newKey;
        }
        
        @Override
        public <T> T attachment() {
            return (T)this.key;
        }
        
        @Override
        public boolean isValid() {
            return !this.canceled.get() && this.key.isValid();
        }
        
        @Override
        public long submit(final IoOps ops) {
            if (!this.isValid()) {
                return -1L;
            }
            final int v = cast(ops).value;
            this.key.interestOps(v);
            return v;
        }
        
        @Override
        public boolean cancel() {
            if (!this.canceled.compareAndSet(false, true)) {
                return false;
            }
            this.key.cancel();
            NioIoHandler.this.cancelledKeys++;
            if (NioIoHandler.this.cancelledKeys >= 256) {
                NioIoHandler.this.cancelledKeys = 0;
                NioIoHandler.this.needsToSelectAgain = true;
            }
            this.handle.unregistered();
            return true;
        }
        
        void close() {
            this.cancel();
            try {
                this.handle.close();
            }
            catch (final Exception e) {
                NioIoHandler.logger.debug("Exception during closing " + this.handle, e);
            }
        }
        
        void handle(final int ready) {
            if (!this.isValid()) {
                return;
            }
            this.handle.handle(this, NioIoOps.eventOf(ready));
        }
    }
}
