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

package io.netty.channel.epoll;

import io.netty.channel.IoEvent;
import java.io.UncheckedIOException;
import java.util.concurrent.atomic.AtomicBoolean;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.channel.IoHandlerContext;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Collections;
import io.netty.channel.Channel;
import java.util.List;
import io.netty.channel.IoRegistration;
import java.io.IOException;
import io.netty.channel.IoOps;
import io.netty.util.internal.StringUtil;
import io.netty.channel.IoHandle;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.internal.ObjectUtil;
import io.netty.channel.SelectStrategyFactory;
import io.netty.channel.DefaultSelectStrategyFactory;
import io.netty.channel.IoHandlerFactory;
import java.util.concurrent.atomic.AtomicLong;
import io.netty.util.concurrent.ThreadAwareExecutor;
import io.netty.util.IntSupplier;
import io.netty.channel.SelectStrategy;
import io.netty.util.collection.IntObjectMap;
import io.netty.channel.unix.FileDescriptor;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.channel.IoHandler;

public class EpollIoHandler implements IoHandler
{
    private static final InternalLogger logger;
    private static final long EPOLL_WAIT_MILLIS_THRESHOLD;
    private long prevDeadlineNanos;
    private FileDescriptor epollFd;
    private FileDescriptor eventFd;
    private FileDescriptor timerFd;
    private final IntObjectMap<DefaultEpollIoRegistration> registrations;
    private final boolean allowGrowing;
    private final EpollEventArray events;
    private final NativeArrays nativeArrays;
    private final SelectStrategy selectStrategy;
    private final IntSupplier selectNowSupplier;
    private final ThreadAwareExecutor executor;
    private static final long AWAKE = -1L;
    private static final long NONE = Long.MAX_VALUE;
    private final AtomicLong nextWakeupNanos;
    private boolean pendingWakeup;
    private int numChannels;
    private static final long MAX_SCHEDULED_TIMERFD_NS = 999999999L;
    
    public static IoHandlerFactory newFactory() {
        return newFactory(0, DefaultSelectStrategyFactory.INSTANCE);
    }
    
    public static IoHandlerFactory newFactory(final int maxEvents, final SelectStrategyFactory selectStrategyFactory) {
        Epoll.ensureAvailability();
        ObjectUtil.checkPositiveOrZero(maxEvents, "maxEvents");
        ObjectUtil.checkNotNull(selectStrategyFactory, "selectStrategyFactory");
        return new IoHandlerFactory() {
            @Override
            public IoHandler newHandler(final ThreadAwareExecutor executor) {
                return new EpollIoHandler(executor, maxEvents, selectStrategyFactory.newSelectStrategy());
            }
            
            @Override
            public boolean isChangingThreadSupported() {
                return true;
            }
        };
    }
    
    EpollIoHandler(final ThreadAwareExecutor executor, final int maxEvents, final SelectStrategy strategy) {
        Epoll.ensureAvailability();
        this.prevDeadlineNanos = Long.MAX_VALUE;
        this.registrations = new IntObjectHashMap<DefaultEpollIoRegistration>(4096);
        this.selectNowSupplier = new IntSupplier() {
            @Override
            public int get() throws Exception {
                return EpollIoHandler.this.epollWaitNow();
            }
        };
        this.nextWakeupNanos = new AtomicLong(-1L);
        this.executor = ObjectUtil.checkNotNull(executor, "executor");
        this.selectStrategy = ObjectUtil.checkNotNull(strategy, "strategy");
        if (maxEvents == 0) {
            this.allowGrowing = true;
            this.events = new EpollEventArray(4096);
        }
        else {
            this.allowGrowing = false;
            this.events = new EpollEventArray(maxEvents);
        }
        this.nativeArrays = new NativeArrays();
        this.openFileDescriptors();
    }
    
    private static EpollIoHandle cast(final IoHandle handle) {
        if (handle instanceof EpollIoHandle) {
            return (EpollIoHandle)handle;
        }
        throw new IllegalArgumentException("IoHandle of type " + StringUtil.simpleClassName(handle) + " not supported");
    }
    
    private static EpollIoOps cast(final IoOps ops) {
        if (ops instanceof EpollIoOps) {
            return (EpollIoOps)ops;
        }
        throw new IllegalArgumentException("IoOps of type " + StringUtil.simpleClassName(ops) + " not supported");
    }
    
    public void openFileDescriptors() {
        boolean success = false;
        FileDescriptor epollFd = null;
        FileDescriptor eventFd = null;
        FileDescriptor timerFd = null;
        try {
            epollFd = (this.epollFd = Native.newEpollCreate());
            eventFd = (this.eventFd = Native.newEventFd());
            try {
                Native.epollCtlAdd(epollFd.intValue(), eventFd.intValue(), Native.EPOLLIN | Native.EPOLLET);
            }
            catch (final IOException e) {
                throw new IllegalStateException("Unable to add eventFd filedescriptor to epoll", e);
            }
            timerFd = (this.timerFd = Native.newTimerFd());
            try {
                Native.epollCtlAdd(epollFd.intValue(), timerFd.intValue(), Native.EPOLLIN | Native.EPOLLET);
            }
            catch (final IOException e) {
                throw new IllegalStateException("Unable to add timerFd filedescriptor to epoll", e);
            }
            success = true;
        }
        finally {
            if (!success) {
                closeFileDescriptor(epollFd);
                closeFileDescriptor(eventFd);
                closeFileDescriptor(timerFd);
            }
        }
    }
    
    private static void closeFileDescriptor(final FileDescriptor fd) {
        if (fd != null) {
            try {
                fd.close();
            }
            catch (final Exception ex) {}
        }
    }
    
    @Override
    public void wakeup() {
        if (!this.executor.isExecutorThread(Thread.currentThread()) && this.nextWakeupNanos.getAndSet(-1L) != -1L) {
            Native.eventFdWrite(this.eventFd.intValue(), 1L);
        }
    }
    
    @Override
    public void prepareToDestroy() {
        final DefaultEpollIoRegistration[] array;
        final DefaultEpollIoRegistration[] copy = array = this.registrations.values().toArray(new DefaultEpollIoRegistration[0]);
        for (final DefaultEpollIoRegistration reg : array) {
            reg.close();
        }
    }
    
    @Override
    public void destroy() {
        try {
            this.closeFileDescriptors();
        }
        finally {
            this.nativeArrays.free();
            this.events.free();
        }
    }
    
    @Override
    public IoRegistration register(final IoHandle handle) throws Exception {
        final EpollIoHandle epollHandle = cast(handle);
        final DefaultEpollIoRegistration registration = new DefaultEpollIoRegistration(this.executor, epollHandle);
        final int fd = epollHandle.fd().intValue();
        Native.epollCtlAdd(this.epollFd.intValue(), fd, EpollIoOps.EPOLLERR.value);
        final DefaultEpollIoRegistration old = this.registrations.put(fd, registration);
        assert !old.isValid();
        if (epollHandle instanceof AbstractEpollChannel.AbstractEpollUnsafe) {
            ++this.numChannels;
        }
        handle.registered();
        return registration;
    }
    
    @Override
    public boolean isCompatible(final Class<? extends IoHandle> handleType) {
        return EpollIoHandle.class.isAssignableFrom(handleType);
    }
    
    int numRegisteredChannels() {
        return this.numChannels;
    }
    
    List<Channel> registeredChannelsList() {
        final IntObjectMap<DefaultEpollIoRegistration> ch = this.registrations;
        if (ch.isEmpty()) {
            return Collections.emptyList();
        }
        final List<Channel> channels = new ArrayList<Channel>(ch.size());
        for (final DefaultEpollIoRegistration registration : ch.values()) {
            if (registration.handle instanceof AbstractEpollChannel.AbstractEpollUnsafe) {
                channels.add(((AbstractEpollChannel.AbstractEpollUnsafe)registration.handle).channel());
            }
        }
        return Collections.unmodifiableList((List<? extends Channel>)channels);
    }
    
    private long epollWait(final IoHandlerContext context, final long deadlineNanos) throws IOException {
        if (deadlineNanos == Long.MAX_VALUE) {
            return Native.epollWait(this.epollFd, this.events, this.timerFd, Integer.MAX_VALUE, 0, EpollIoHandler.EPOLL_WAIT_MILLIS_THRESHOLD);
        }
        final long totalDelay = context.delayNanos(System.nanoTime());
        final int delaySeconds = (int)Math.min(totalDelay / 1000000000L, 2147483647L);
        final int delayNanos = (int)Math.min(totalDelay - delaySeconds * 1000000000L, 999999999L);
        return Native.epollWait(this.epollFd, this.events, this.timerFd, delaySeconds, delayNanos, EpollIoHandler.EPOLL_WAIT_MILLIS_THRESHOLD);
    }
    
    private int epollWaitNoTimerChange() throws IOException {
        return Native.epollWait(this.epollFd, this.events, false);
    }
    
    private int epollWaitNow() throws IOException {
        return Native.epollWait(this.epollFd, this.events, true);
    }
    
    private int epollBusyWait() throws IOException {
        return Native.epollBusyWait(this.epollFd, this.events);
    }
    
    private int epollWaitTimeboxed() throws IOException {
        return Native.epollWait(this.epollFd, this.events, 1000);
    }
    
    @Override
    public int run(final IoHandlerContext context) {
        int handled = 0;
        try {
            int strategy = this.selectStrategy.calculateStrategy(this.selectNowSupplier, !context.canBlock());
            switch (strategy) {
                case -2: {
                    if (context.shouldReportActiveIoTime()) {
                        context.reportActiveIoTime(0L);
                    }
                    return 0;
                }
                case -3: {
                    strategy = this.epollBusyWait();
                    break;
                }
                case -1: {
                    if (this.pendingWakeup) {
                        strategy = this.epollWaitTimeboxed();
                        if (strategy != 0) {
                            break;
                        }
                        EpollIoHandler.logger.warn("Missed eventfd write (not seen after > 1 second)");
                        this.pendingWakeup = false;
                        if (!context.canBlock()) {
                            break;
                        }
                    }
                    long curDeadlineNanos = context.deadlineNanos();
                    if (curDeadlineNanos == -1L) {
                        curDeadlineNanos = Long.MAX_VALUE;
                    }
                    this.nextWakeupNanos.set(curDeadlineNanos);
                    try {
                        if (context.canBlock()) {
                            if (curDeadlineNanos == this.prevDeadlineNanos) {
                                strategy = this.epollWaitNoTimerChange();
                            }
                            else {
                                final long result = this.epollWait(context, curDeadlineNanos);
                                strategy = Native.epollReady(result);
                                this.prevDeadlineNanos = (Native.epollTimerWasUsed(result) ? curDeadlineNanos : Long.MAX_VALUE);
                            }
                        }
                    }
                    finally {
                        if (this.nextWakeupNanos.get() == -1L || this.nextWakeupNanos.getAndSet(-1L) == -1L) {
                            this.pendingWakeup = true;
                        }
                    }
                    break;
                }
            }
            if (strategy > 0) {
                handled = strategy;
                if (context.shouldReportActiveIoTime()) {
                    final long activeIoStartTimeNanos = System.nanoTime();
                    if (this.processReady(this.events, strategy)) {
                        this.prevDeadlineNanos = Long.MAX_VALUE;
                    }
                    final long activeIoEndTimeNanos = System.nanoTime();
                    context.reportActiveIoTime(activeIoEndTimeNanos - activeIoStartTimeNanos);
                }
                else if (this.processReady(this.events, strategy)) {
                    this.prevDeadlineNanos = Long.MAX_VALUE;
                }
            }
            else if (context.shouldReportActiveIoTime()) {
                context.reportActiveIoTime(0L);
            }
            if (this.allowGrowing && strategy == this.events.length()) {
                this.events.increase();
            }
        }
        catch (final Error e) {
            throw e;
        }
        catch (final Throwable t) {
            this.handleLoopException(t);
        }
        return handled;
    }
    
    void handleLoopException(final Throwable t) {
        EpollIoHandler.logger.warn("Unexpected exception in the selector loop.", t);
        try {
            Thread.sleep(1000L);
        }
        catch (final InterruptedException ex) {}
    }
    
    private boolean processReady(final EpollEventArray events, final int ready) {
        boolean timerFired = false;
        for (int i = 0; i < ready; ++i) {
            final int fd = events.fd(i);
            if (fd == this.eventFd.intValue()) {
                this.pendingWakeup = false;
            }
            else if (fd == this.timerFd.intValue()) {
                timerFired = true;
            }
            else {
                final long ev = events.events(i);
                final DefaultEpollIoRegistration registration = this.registrations.get(fd);
                if (registration != null) {
                    registration.handle(ev);
                }
                else {
                    try {
                        Native.epollCtlDel(this.epollFd.intValue(), fd);
                    }
                    catch (final IOException ex) {}
                }
            }
        }
        return timerFired;
    }
    
    public void closeFileDescriptors() {
        while (this.pendingWakeup) {
            try {
                final int count = this.epollWaitTimeboxed();
                if (count == 0) {
                    break;
                }
                for (int i = 0; i < count; ++i) {
                    if (this.events.fd(i) == this.eventFd.intValue()) {
                        this.pendingWakeup = false;
                        break;
                    }
                }
            }
            catch (final IOException ex) {}
        }
        try {
            this.eventFd.close();
        }
        catch (final IOException e) {
            EpollIoHandler.logger.warn("Failed to close the event fd.", e);
        }
        try {
            this.timerFd.close();
        }
        catch (final IOException e) {
            EpollIoHandler.logger.warn("Failed to close the timer fd.", e);
        }
        try {
            this.epollFd.close();
        }
        catch (final IOException e) {
            EpollIoHandler.logger.warn("Failed to close the epoll fd.", e);
        }
    }
    
    static {
        logger = InternalLoggerFactory.getInstance(EpollIoHandler.class);
        EPOLL_WAIT_MILLIS_THRESHOLD = SystemPropertyUtil.getLong("io.netty.channel.epoll.epollWaitThreshold", 10L);
    }
    
    private final class DefaultEpollIoRegistration implements IoRegistration
    {
        private final ThreadAwareExecutor executor;
        private final AtomicBoolean canceled;
        final EpollIoHandle handle;
        
        DefaultEpollIoRegistration(final ThreadAwareExecutor executor, final EpollIoHandle handle) {
            this.canceled = new AtomicBoolean();
            this.executor = executor;
            this.handle = handle;
        }
        
        @Override
        public <T> T attachment() {
            return (T)EpollIoHandler.this.nativeArrays;
        }
        
        @Override
        public long submit(final IoOps ops) {
            final EpollIoOps epollIoOps = cast(ops);
            try {
                if (!this.isValid()) {
                    return -1L;
                }
                Native.epollCtlMod(EpollIoHandler.this.epollFd.intValue(), this.handle.fd().intValue(), epollIoOps.value);
                return epollIoOps.value;
            }
            catch (final IOException e) {
                throw new UncheckedIOException(e);
            }
        }
        
        @Override
        public boolean isValid() {
            return !this.canceled.get();
        }
        
        @Override
        public boolean cancel() {
            if (!this.canceled.compareAndSet(false, true)) {
                return false;
            }
            if (this.executor.isExecutorThread(Thread.currentThread())) {
                this.cancel0();
            }
            else {
                this.executor.execute(this::cancel0);
            }
            return true;
        }
        
        private void cancel0() {
            final int fd = this.handle.fd().intValue();
            final DefaultEpollIoRegistration old = EpollIoHandler.this.registrations.remove(fd);
            if (old != null) {
                if (old != this) {
                    EpollIoHandler.this.registrations.put(fd, old);
                    return;
                }
                if (old.handle instanceof AbstractEpollChannel.AbstractEpollUnsafe) {
                    EpollIoHandler.this.numChannels--;
                }
                if (this.handle.fd().isOpen()) {
                    try {
                        Native.epollCtlDel(EpollIoHandler.this.epollFd.intValue(), fd);
                    }
                    catch (final IOException e) {
                        EpollIoHandler.logger.debug("Unable to remove fd {} from epoll {}", (Object)fd, EpollIoHandler.this.epollFd.intValue());
                    }
                }
                this.handle.unregistered();
            }
        }
        
        void close() {
            try {
                this.cancel();
            }
            catch (final Exception e) {
                EpollIoHandler.logger.debug("Exception during canceling " + this, e);
            }
            try {
                this.handle.close();
            }
            catch (final Exception e) {
                EpollIoHandler.logger.debug("Exception during closing " + this.handle, e);
            }
        }
        
        void handle(final long ev) {
            this.handle.handle(this, EpollIoOps.eventOf((int)ev));
        }
    }
}
