/*---------------------------------------------------------------------------*/
/* iomux.cpp                                                                 */
/* copyright (c) innovaphone 2013                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/
#include "platform/platform.h"
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#ifdef __linux__
#include <sys/epoll.h>
#define IOMUX_USE_EPOLL
#else
#include <sys/poll.h>
#define IOMUX_POLLOUTS_UNTIL_IDLE 8
#endif
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include <list>
#include <time.h>
#include "common/os/iomux.h"
#include "iomux.h"

#define		MAXEVENTS	2000				// max. events per epoll run
#define     MAXFD		500					// default maximum number of file descriptors

#ifndef POLLRDHUP
#define POLLRDHUP 0
#endif
#ifndef EPOLLRDHUP
#define EPOLLRDHUP 0
#endif

typedef std::list<ISignalConsumer *> TConsumerList;
typedef std::list<IShutdownHandler *> TShutdownHandlerList;
typedef std::list<IChildExitedHandler *> TChildExitedHandlerList;
/*---------------------------------------------------------------------------*/
/* class IoCancelEpollWaitHelper                                             */
/*---------------------------------------------------------------------------*/
class IoCancelEpollWaitHelper : public UIoContext {
    int pipeFdOut;
    int pipeFdIn;
    IIoMux * const iomux;

public:
    IoCancelEpollWaitHelper(IIoMux * const iomux)
        : pipeFdOut(0),
          pipeFdIn(0),
          iomux(iomux)
    {
    }

    ~IoCancelEpollWaitHelper()
    {
        close(pipeFdOut);
        iomux->FDClose(this);
    }


    void Init()
    {
        int fd[2];
        if (pipe(fd) != 0) {
            debug->printf("IoCancelEpollWaitHelper::Init pipe() failed (%u : %s)", errno, strerror(errno));
            ASSERT(false, "IoCancelEpollWaitHelper::Init pipe() failed");
        }

        pipeFdIn = fd[0];
        pipeFdOut = fd[1];

        iomux->FDUnblock(pipeFdIn);
        iomux->FDAdd(pipeFdIn, this, false);
    }

    void IoContextNotify(bool indRX, bool indTX)  override;

    void WriteByte(byte data)
    {
        int r = write(pipeFdOut, &data, sizeof(data));
        if (r != 1)
            debug->printf("IoCancelEpollWaitHelper::WriteByte write() error (%u : %s)", errno, strerror(errno));
    }
};



/*---------------------------------------------------------------------------*/
/* class IoMux  		                                                     */
/*---------------------------------------------------------------------------*/

class UIoExecAnchor : public UIoExec {
public:
    UIoExecAnchor() {};
    ~UIoExecAnchor() {};
    virtual void IoExec(void * execContext) { ASSERT(false, "UIoExec::IoExec not implemented"); };
};

class IoMux : public IIoMuxEx {
    TConsumerList              consumers;
    time_t                     last_sec;
    dword                      last_ms;
    dword                      ticks;
    dword                      timer_count;
    class ITimer *             timer_first;
    class ITimer *             timer_last;
    class btree *              timer_btree;
    TShutdownHandlerList       shutdownHandlerList;
    TChildExitedHandlerList    childExitedHandlerList;
    IIoMainLoopExtension *     mainLoopExtension;
    IoCancelEpollWaitHelper    epollWaitCancel;
    
    pthread_mutex_t lock;
    bool isRunning;
    bool isWaitingForEPoll;
    bool inTimerListRun;
    bool shutDown;

    struct {
        unsigned rlimitNoFile;
        bool locking;

        void Init()
        {
            memset(this, 0, sizeof(*this));
        }

    } cfg;

    struct {
        int		numFd, maxFd;
        int		efd;
#ifdef IOMUX_USE_EPOLL
        struct	epoll_event events[MAXEVENTS];
#else
        int     pollfdMax;
        int     pollfdCount;
        int     pollfdUsed;
        short   pollouts[MAXEVENTS];
        bool    notifyUsed[MAXEVENTS];
        UIoContext * contexts[MAXEVENTS+1];
        struct pollfd events[1+MAXEVENTS+1];
#endif

        void FdInit()
        {
#ifdef IOMUX_USE_EPOLL
            memset(events, 0, sizeof(events));
            this->efd = epoll_create(1);
#else
            pollfdMax = 0;
            pollfdCount = 0;
            pollfdUsed = 0;
#endif
            numFd = 1;
            maxFd = MAXFD;
        }

        void FDAdd(int fd, UIoContext * context, bool pollOut)
        {
            if (!context->isOpened) {
                context->isOpened = true;
#ifdef IOMUX_USE_EPOLL
                struct epoll_event	ev;
                memset(&ev, 0, sizeof(ev));
                ev.events = EPOLLIN | EPOLLET;	// Edge-triggered
                if (pollOut)
                    ev.events |= EPOLLOUT;
                ev.data.ptr = context;
                if (epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev) < 0) {
                    ASSERT(false, "IoMux::FDAdd epoll_ctl");
                }
#else
                contexts[pollfdMax] = 0;
                int i = pollfdUsed;
                while(contexts[i]) i++;
                pollouts[i] = pollOut ? IOMUX_POLLOUTS_UNTIL_IDLE + 1 : 0;
                notifyUsed[i] = false;
                contexts[i] = context;
                events[1+i].fd = fd;
                events[1+i].events = (pollouts[i] != 0) ? POLLHUP | POLLRDHUP | POLLIN | POLLOUT : POLLHUP | POLLRDHUP | POLLIN;
                events[1+i].revents = 0;
                if(i == pollfdMax) {
                    pollfdMax = i+1;
                }
#endif
                numFd++;
            }
        }

        void FDClose(UIoContext * context, bool closeFd)
        {
            int fd = context->GetFd();
            if (fd >= 0) {
                if (context->isOpened) {
                    context->isOpened = false;
                    // ensure delete of kernel fd object to avaid a raise condition
#ifdef IOMUX_USE_EPOLL
                    epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL);    // ignore return value, because if this fails, we can't do anything at all                    
#else
                    int i = 0;
                    contexts[pollfdMax] = context;
                    while(contexts[i] != context) i++;
                    if (i < pollfdMax) {
                        contexts[i] = 0;
                        (pollfdCount)--;
                    }
#endif
                    numFd--;
                }
                if(closeFd) {
                    if (close(fd) < 0) {
                        debug->printf("IoMux::FDClose close() error (%u : %s)", errno, strerror(errno));
                    }
                }                
            }
        }
        
        void FDClose(UIoContext * context)
        {
            this->FDClose(context, true);
        }

        void FDRem(UIoContext * context)
        {
            this->FDClose(context, false);
        }

        void FDModify(UIoContext * context, bool pollOut)
        {
            // quick fix for ios
            int fd = context->GetFd();
            FDClose(context, false);
            FDAdd(fd, context, pollOut);
        }

#ifdef IOMUX_USE_EPOLL
#else
        void FDNotify(UIoContext * context, bool notifyRX, bool notifyTX)
        {
            int i = 0;
            contexts[pollfdMax] = context;
            while(contexts[i] != context) i++;
            if (i < pollfdMax) {
                notifyUsed[i] = true;
                events[1+i].events |= (notifyRX ? POLLIN : 0) | (notifyTX ? POLLOUT : 0);
            }
        }
#endif
    } mux;

    struct {
        bool signalSIGHUP;
        bool signalSIGCHLD;
        bool signalShutdown;
        bool signalSIGUSR1;

        void Clear() { memset(this, 0, sizeof(*this)); }
    } maintenance;

    UIoExecAnchor execAnchor;

    void Run();
    void Terminate();
    void RegisterSignalConsumer(ISignalConsumer * consumer);
    void RegisterShutdownHandler(IShutdownHandler * shutdownHandler);
    void RegisterChildExitedHandler(IChildExitedHandler * childExitedHandler);
    void UnRegisterChildExitedHandler(IChildExitedHandler * childExitedHandler);
    void SetMainLoopExtension(IIoMainLoopExtension * mainLoopExtension);
    void SetConfig(int rlimitNoFile);
    void Lock();
    void UnLock();
    void SetExec(UIoExec * ioContext, void * execContext);
    void SetExecLocked(UIoExec * ioContext, void * execContext);
    void CancelSetExec(UIoExec * const ioContext);
    void ExecListRun();
    void TimerListRun();
    int  TimerNextMs();
    void OnShutdown();
    void Shutdown();
    void ShutdownTimeout();
    void ShutdownComplete(IShutdownHandler * shutdownHandler);
    void OnChildExit(pid_t pid, int signr, int exitCode, bool sigFault);

public:

    static IoMux * ioMuxSignalHandler;
    
    IoMux(bool locking, unsigned rlimitNoFile);
    ~IoMux();

    void SetFd();
    void FDAdd(int fd, UIoContext * context, bool pollOut);
    void FDClose(UIoContext * context);
    void FDRem(UIoContext * context);
    void FDModify(UIoContext * context, bool pollOut);
    void FDUnblock(int fd);
#ifdef IOMUX_USE_EPOLL
    void FDNotify(UIoContext * context, bool notifyRX, bool notifyTX) {};
#else
    void FDNotify(UIoContext * context, bool notifyRX, bool notifyTX) { this->mux.FDNotify(context, notifyRX, notifyTX); };
#endif
    bool CanAcceptFD();
    void OnSignal(int signr);
    void Signal(int signr);
    void CheckSignals();
    void TimerStart(ITimer * timer, unsigned timeoutMs);
    void TimerStartLocked(ITimer * timer, unsigned timeoutMs);
    void TimerCancel(ITimer * timer);
    void TimerCancelLocked(ITimer * timer);
};

IoMux * IoMux::ioMuxSignalHandler = NULL;

IoMux::IoMux(bool locking, unsigned rlimitNoFile)
    : epollWaitCancel(this),
      isRunning(true),
      isWaitingForEPoll(false),
      shutDown(false)
{
#ifdef IOMUX_USE_EPOLL
    struct timespec tv;
    clock_gettime(CLOCK_MONOTONIC, &tv);
    last_ms = (dword)(tv.tv_nsec / 1000000);
#else
	struct timeval tv;
    gettimeofday(&tv, NULL);
    last_ms = (dword)(tv.tv_usec / 1000);
#endif
    last_sec = tv.tv_sec;
    ticks = 0;
    timer_count = 0;
    timer_first = NULL;
    timer_last = NULL;
    timer_btree = NULL;
    mainLoopExtension = NULL;
    if (!IoMux::ioMuxSignalHandler) {
        IoMux::ioMuxSignalHandler = this;
    }
    this->mux.FdInit();
    this->cfg.Init();
    this->maintenance.Clear();
    this->cfg.locking = locking;
    this->cfg.rlimitNoFile = rlimitNoFile;
    execAnchor.ioExecNext = &execAnchor;
    execAnchor.ioExecPrev = &execAnchor;

    epollWaitCancel.Init();
    pthread_mutex_init(&lock, 0);
    this->Lock();
    inTimerListRun = false;
}

IoMux::~IoMux()
{
    pthread_mutex_destroy(&lock);
    if (this == IoMux::ioMuxSignalHandler) {
        IoMux::ioMuxSignalHandler = NULL;
    }
}

class IIoMux * IIoMux::Create(bool locking, unsigned rlimitNoFile, bool backtrace)
{
    class IIoMux * iomux = new IoMux(locking, rlimitNoFile);
    debug->SetIIoMux(iomux);
    return iomux;
}

void IoMux::FDAdd(int fd, UIoContext * context, bool pollOut)
{
    context->SetFd(fd);
    this->mux.FDAdd(fd, context, pollOut);
}

void IoMux::FDClose(UIoContext * context)
{
    this->mux.FDClose(context);
}

void IoMux::FDRem(UIoContext * context)
{
    this->mux.FDRem(context);
}

void IoMux::FDModify(UIoContext * context, bool pollOut)
{
    this->mux.FDModify(context, pollOut);
}

void IoMux::FDUnblock(int fd)
{
    int flags, err;

    flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0) {
        ASSERT(false, "IoMux::FDUnblock fcntl F_GETFL");
    }

    flags |= O_NONBLOCK;
    err = fcntl(fd, F_SETFL, flags);
    if (err <0) {
        ASSERT(false, "IoMux::FDUnblock fcntl F_SETFL");
    }
}

bool IoMux::CanAcceptFD()
{
    if (mux.numFd >= mux.maxFd) {
        debug->printf("IoMux::CanAcceptFD out of FDs cur:%i!", mux.numFd);
        return false;
    }
    return true;
}

static void sigHandler(int signr)
{
    IoMux::ioMuxSignalHandler->Signal(signr);
    switch (signr) {
    case SIGHUP:
        break;
    case SIGPIPE:
        break;
    }
}

void IoMux::RegisterSignalConsumer(ISignalConsumer * consumer)
{
    this->consumers.push_back(consumer);
}

void IoMux::RegisterShutdownHandler(IShutdownHandler * shutdownHandler)
{
    this->shutdownHandlerList.push_back(shutdownHandler);
}

void IoMux::RegisterChildExitedHandler(IChildExitedHandler * childExitedHandler)
{
    this->childExitedHandlerList.push_back(childExitedHandler);
}

void IoMux::UnRegisterChildExitedHandler(IChildExitedHandler * childExitedHandler)
{
    this->childExitedHandlerList.remove(childExitedHandler);
}

void IoMux::SetMainLoopExtension(IIoMainLoopExtension * mainLoopExtension)
{
    this->mainLoopExtension = mainLoopExtension;
}

void IoMux::SetConfig(int rlimitNoFile)
{
#define FD_SPARE_COUNT 10
    if ((rlimitNoFile - FD_SPARE_COUNT) > mux.maxFd)
        mux.maxFd = rlimitNoFile - FD_SPARE_COUNT;
    else
        mux.maxFd = MAXFD;
    debug->printf("IoMux::SetConfig maxFd:%i", mux.maxFd);
}

void IoMux::Lock()
{
    pthread_mutex_lock(&lock);
}

void IoMux::UnLock()
{
    pthread_mutex_unlock(&lock);
}

void IoMux::SetExec(UIoExec * const ioContext, void * execContext)
{
    if (ioContext->ioExecPrev == ioContext) {
        ioContext->execContext = execContext;
        execAnchor.ioExecPrev->ioExecNext = ioContext;
        ioContext->ioExecPrev = execAnchor.ioExecPrev;
        ioContext->ioExecNext = &execAnchor;
        execAnchor.ioExecPrev = ioContext;
    }
    else {
        UIoExec * ioExec = new UIoExecAnchor;
        ioExec->contextOwner = ioContext;
        ioExec->execContext = execContext;

        ioContext->contextPrev->contextNext = ioExec;
        ioExec->contextPrev = ioContext->contextPrev;
        ioExec->contextNext = ioContext;
        ioContext->contextPrev = ioExec;

        execAnchor.ioExecPrev->ioExecNext = ioExec;
        ioExec->ioExecPrev = execAnchor.ioExecPrev;
        ioExec->ioExecNext = &execAnchor;
        execAnchor.ioExecPrev = ioExec;
    }
    // This is only of interrest, when SetExec() will be called from an other thread
    // while the main thread is waiting for an epoll event.
    if (isWaitingForEPoll) {
        isWaitingForEPoll = false;
        epollWaitCancel.WriteByte(0);
    }
}

void IoMux::SetExecLocked(UIoExec * const ioContext, void * execContext)
{
    Lock();
    SetExec(ioContext, execContext);
    UnLock();
}

void IoMux::CancelSetExec(UIoExec * const ioContext)
{
    while (ioContext->contextNext != ioContext) {
        delete ioContext->contextNext;
    }
    ioContext->ioExecNext->ioExecPrev = ioContext->ioExecPrev;
    ioContext->ioExecPrev->ioExecNext = ioContext->ioExecNext;
    ioContext->ioExecNext = ioContext;
    ioContext->ioExecPrev = ioContext;
}


void IoMux::OnSignal(int signr)
{
    for (TConsumerList::iterator i = consumers.begin(); i != consumers.end(); ++i) {
        ((ISignalConsumer *)*i)->OnSignal(signr);
    }
}

void IoMux::Shutdown()
{
    OnShutdown();
}

void IoMux::ShutdownComplete(IShutdownHandler * shutdownHandler)
{
    shutdownHandlerList.remove(shutdownHandler);
    if (shutdownHandlerList.empty() && shutDown) Terminate();
}

void IoMux::OnShutdown()
{
    debug->printf("IoMux::OnShutdown e=%d", shutdownHandlerList.empty());
    shutDown = true;
    if (shutdownHandlerList.empty()) Terminate();
    else {
        for (TShutdownHandlerList::iterator i = shutdownHandlerList.begin(); i != shutdownHandlerList.end();) {
            TShutdownHandlerList::iterator t = i++;
            ((IShutdownHandler *)*t)->Shutdown();
        }
    }
}

void IoMux::ShutdownTimeout()
{
    for (TShutdownHandlerList::iterator i = shutdownHandlerList.begin(); i != shutdownHandlerList.end(); ++i) {
        ((IShutdownHandler *)*i)->ShutdownTimeout();
    }
}

void IoMux::OnChildExit(pid_t pid, int signr, int exitCode, bool sigFault)
{
    for (TChildExitedHandlerList::iterator i = childExitedHandlerList.begin(); i != childExitedHandlerList.end(); ++i) {
        ((IChildExitedHandler *)*i)->ChildExited(pid, signr, exitCode, sigFault);
    }
}

void IoMux::Signal(int signr)
{
    switch (signr) {
    case SIGHUP:
        this->maintenance.signalSIGHUP = true;
        break;
    case SIGUSR1:
        this->maintenance.signalSIGUSR1 = true;
        break;
    case SIGCHLD:
        this->maintenance.signalSIGCHLD = true;
        break;
    case SIGTERM:
    case SIGKILL:
    case SIGINT:
        this->maintenance.signalShutdown = true;
        break;
    }
    epollWaitCancel.WriteByte(1);
}

void IoMux::Run()
{
    dword ms;
    struct {
        int signr;
    } signals[] = {
#if (defined(_IOS_) && !TARGET_OS_IPHONE)
        { SIGTERM }, { SIGKILL }, { SIGHUP }, { SIGINT }, { SIGPIPE }, { SIGUSR1 }
#else
        { SIGTERM }, { SIGKILL }, { SIGHUP }, { SIGINT }, { SIGPIPE }, { SIGUSR1 }, { SIGCHLD }
#endif
    };
#ifdef IOMUX_USE_EPOLL
    struct timespec tv;
#else
	struct timeval tv;
    UIoContext * context;
    int i, fd, pollResult;
    short pollouts;
    bool notifyUsed;
    unsigned revents_acc;
#endif

    for (unsigned i = 0; i < (sizeof(signals) / sizeof(signals[0])); i++) {
        signal(signals[i].signr, sigHandler);
    }

    if (this->cfg.rlimitNoFile)
        this->SetConfig((int)this->cfg.rlimitNoFile);

    while (isRunning) {
        // Handle pending SetExec() callbacks immediately...
        int timeOutValue = (execAnchor.ioExecNext != &execAnchor) ? 0 : this->TimerNextMs();
        if (mainLoopExtension) {
            mainLoopExtension->IoMainLoopExtension(timeOutValue);
        }
#ifdef IOMUX_USE_EPOLL
        isWaitingForEPoll = true;
        this->UnLock();
        int nfds = epoll_wait(mux.efd, mux.events, MAXEVENTS, timeOutValue);
        isWaitingForEPoll = false;
        if (nfds < 0) {
            int e = errno;
            switch (e) {
            case EINTR:
                this->Lock();
                continue;
            }
            ASSERT(false, "IoMux::Run epoll_wait");
        }
        this->Lock();
        clock_gettime(CLOCK_MONOTONIC, &tv);
        ms = (dword)(tv.tv_nsec / 1000000);
        ticks += (dword)((dword)(tv.tv_sec - last_sec) * 1000 + ms - last_ms);
        last_sec = tv.tv_sec;
        last_ms = ms;
        for (int i = 0; i < nfds; i++) {
            UIoContext * context = (UIoContext *)mux.events[i].data.ptr;
            unsigned events = mux.events[i].events;
            if (events & (EPOLLHUP | EPOLLRDHUP)) {
                context->isHangup = true;
            }
            if ((events & EPOLLERR) || !(events & (EPOLLHUP | EPOLLRDHUP | EPOLLIN | EPOLLOUT))) {
                // An error has occured on this fd, or the socket is not  ready for reading
                if (!(events & (EPOLLHUP | EPOLLRDHUP))) {
                    debug->printf("IoMux::Run epoll error:%08x!", events);
                }
                context->isError = true;
            }
            context->IoContextNotify(((events & EPOLLIN) != 0), ((events & EPOLLOUT) != 0));
        }
#else
        if(mux.pollfdCount != mux.pollfdMax) {
            if(mux.pollfdCount != mux.pollfdMax) {
                mux.pollfdCount = 0;
                for(i = 0; i < mux.pollfdMax; i++) {
                    if(mux.contexts[i]) {
                        mux.contexts[mux.pollfdCount] = mux.contexts[i];
                        mux.pollouts[mux.pollfdCount] = mux.pollouts[i];
                        mux.notifyUsed[mux.pollfdCount] = mux.notifyUsed[i];
                        mux.events[1+mux.pollfdCount].fd = mux.events[1+i].fd;
                        mux.events[1+mux.pollfdCount].events = mux.events[1+i].events;
                        mux.events[1+mux.pollfdCount].revents = 0;
                        mux.pollfdCount++;
                    }
                }
                mux.pollfdMax = mux.pollfdCount;
            }
            mux.events[0].events = 1;
            mux.events[1+mux.pollfdCount].events = 0;
            mux.pollfdUsed = 0;
            i = mux.pollfdCount;
            while(mux.events[1+mux.pollfdUsed].events != 0) {
                mux.pollfdUsed++;
            }
            while(mux.events[i].events == 0) {
                i--;
            }
            while(mux.pollfdUsed != i) {
                context = mux.contexts[mux.pollfdUsed];
                mux.contexts[mux.pollfdUsed] = mux.contexts[i-1];
                mux.contexts[i-1] = context;
                pollouts = mux.pollouts[mux.pollfdUsed];
                mux.pollouts[mux.pollfdUsed] = mux.pollouts[i-1];
                mux.pollouts[i-1] = pollouts;
                notifyUsed = mux.notifyUsed[mux.pollfdUsed];
                mux.notifyUsed[mux.pollfdUsed] = mux.notifyUsed[i-1];
                mux.notifyUsed[i-1] = notifyUsed;
                fd = mux.events[1+mux.pollfdUsed].fd;
                mux.events[1+mux.pollfdUsed].fd = mux.events[i].fd;
                mux.events[i].fd = fd;
                mux.events[1+mux.pollfdUsed].events = mux.events[i].events;
                mux.events[i].events = 0;
                do {
                    mux.pollfdUsed++;
                }
                while(mux.events[1+mux.pollfdUsed].events != 0);
                do {
                    i--;
                }
                while(mux.events[i].events == 0);
            }
        }
        isWaitingForEPoll = true;
        this->UnLock();
        pollResult = ::poll(&(mux.events[1]), mux.pollfdUsed, timeOutValue);
        isWaitingForEPoll = false;
        this->Lock();
        gettimeofday(&tv, NULL);
        ms = (dword)(tv.tv_usec / 1000);
        ticks += (dword)((dword)(tv.tv_sec - last_sec) * 1000 + ms - last_ms);
        last_sec = tv.tv_sec;
        last_ms = ms;
        revents_acc = 0;
        if (pollResult > 0) {
            i = 0;
            mux.events[1+mux.pollfdUsed].revents = 1;
            while(mux.events[1+i].revents == 0) {
                i++;
            }
            while(i < mux.pollfdUsed) {
                if(mux.contexts[i]) {
                    UIoContext * context = mux.contexts[i];
                    unsigned events = mux.events[1+i].revents;
                    if (events & (POLLHUP | POLLRDHUP)) {
                        context->isHangup = true;
                    }
                    if ((events & POLLERR) || !(events & (POLLHUP | POLLRDHUP | POLLIN | POLLOUT))) {
                        // An error has occured on this fd, or the socket is not  ready for reading
                        if (!(events & (POLLHUP | POLLRDHUP))) {
                            debug->printf("IoMux::Run poll error:%08x!", events);
                        }
                        context->isError = true;
                    }
                    if (mux.notifyUsed[i]) {
                        if (events & POLLIN) {
                            mux.events[1+i].events &= ~POLLIN;
                        }
                        if (events & POLLOUT) {
                            mux.events[1+i].events &= ~POLLOUT;
                        }
                        context->IoContextNotify(((events & POLLIN) != 0), ((events & POLLOUT) != 0));
                    }
                    else {
                        context->IoContextNotify(((events & POLLIN) != 0), ((events & POLLOUT) != 0));
                        if ((events & POLLOUT) && (mux.pollouts[i] > 1)) {
                            mux.pollouts[i]--;
                            if (mux.pollouts[i] == 1) {
                                mux.events[1+i].events &= ~POLLOUT;
                            }
                        }
                    }
                    revents_acc |= events;
                    mux.events[1+i].revents = 0;
                    mux.events[1+mux.pollfdUsed].revents = 1;
                }
                do {
                    i++;
                }
                while(mux.events[1+i].revents == 0);
            }
            mux.events[1+mux.pollfdUsed].revents = 0;
        }
        if ((revents_acc & ~POLLOUT) || (execAnchor.ioExecNext != &execAnchor) || ((((int) ticks) > 0) && timer_first && (timer_first->delta <= ticks))) {
            for (i = 0; i < mux.pollfdUsed; i++) {
                if(mux.contexts[i] && (mux.pollouts[i] != 0) && !mux.notifyUsed[i]) {
                    mux.pollouts[i] = IOMUX_POLLOUTS_UNTIL_IDLE + 1;
                    mux.events[1+i].events |= POLLOUT;
                }
            }
        }
#endif
        this->TimerListRun();
        this->ExecListRun();
    }
    for (unsigned i = 0; i < (sizeof(signals) / sizeof(signals[0])); i++) {
        signal(signals[i].signr, SIG_DFL);
    }
    this->UnLock();
}

void IoMux::Terminate()
{
    isRunning = false;
    epollWaitCancel.WriteByte(0);
}

void IoMux::TimerStartLocked(ITimer * timer, unsigned value)
{
    Lock();
    TimerStart(timer, value);
    UnLock();
}

void IoMux::TimerStart(ITimer * timer, unsigned value)
{
    class ITimer * prev;
    class ITimer * next;
    
    if(timer->running) TimerCancel(timer);

    if (inTimerListRun) {
        // Make sure that interval timers are triggered at their nominal rate
        if (value > 0x7fffffff) {
            debug->printf("IoMux::TimerStart timer:%p started with a very high value:%u, lowering value to:%u (caller:%x)", timer, (dword)value, (dword)0x7fffffff, BUILTIN_CALLER);
            value = 0x7fffffff;
        }
    }
    else {
        // Avoid early timer expiry if substantial work was done in the thread we are called from
        if (value < 500) {
#ifdef IOMUX_USE_EPOLL
            struct timespec tv;
            clock_gettime(CLOCK_MONOTONIC, &tv);
            dword ms = (dword)(tv.tv_nsec / 1000000);
#else
    	    struct timeval tv;
            gettimeofday(&tv, NULL);
            dword ms = (dword)(tv.tv_usec / 1000);
#endif
            ticks += (dword)((dword)(tv.tv_sec - last_sec) * 1000 + ms - last_ms);
            last_sec = tv.tv_sec;
            last_ms = ms;
        }
        if (value > 0x7fffffff - ((((int) ticks) > 0) ? ticks : 0)) {
            debug->printf("IoMux::TimerStart timer:%p started with a very high value:%u, lowering value to:%u (caller:%x)", timer, (dword)value, (dword)(0x7fffffff - ticks), BUILTIN_CALLER);
            value = 0x7fffffff - ((((int) ticks) > 0) ? ticks : 0);
        }
        value += (((int) ticks) > 0) ? ticks : 0;
    }

    timer->expires = timer_count + value;
    class btree * b = timer_btree->btree_find_next_left(&timer->expires);
    if (b) {
        next = (class ITimer *)b;
        prev = next->prev;
    }
    else {
        next = 0;
        prev = timer_last;
    }
    if (!prev || prev->expires != timer->expires) {
        timer_btree = timer_btree->btree_put(timer);
    }
    if (prev) value = timer->expires - prev->expires;

    timer->delta = value;
    timer->next = next;
    timer->prev = prev;
    if (next) {
        next->delta -= value;
        next->prev = timer;
    }
    else {
        timer_last = timer;
    }
    if (prev) {
        prev->next = timer;
    }
    else {
        timer_first = timer;
    }
    timer->running = true;
}

void IoMux::TimerCancelLocked(ITimer * timer)
{
    Lock();
    TimerCancel(timer);
    UnLock();
}

void IoMux::TimerCancel(ITimer * timer)
{
    if (timer->running) {
        if (!timer->prev || timer->prev->expires != timer->expires) {
            timer_btree = timer_btree->btree_get(timer);
            if (timer->next && timer->next->expires == timer->expires) {
                timer_btree = timer_btree->btree_put(timer->next);
            }
        }

        if (timer->next) {
            timer->next->delta += timer->delta;
            timer->next->prev = timer->prev;
        }
        else {
            timer_last = timer->prev;
        }
        if (timer->prev) {
            timer->prev->next = timer->next;
        }
        else {
            timer_first = timer->next;
        }
        timer->running = false;
    }
}

void IoMux::TimerListRun()
{
    inTimerListRun = true;
    while(((int) ticks) > 0) {
        if(timer_first) {
            int d = MIN(ticks,timer_first->delta);
            ticks -=d;
            timer_count += d;
            timer_first->delta -=d;
            while(timer_first && !timer_first->delta) {
                class ITimer * timer;

                timer = timer_first;
                timer_first = timer->next;
                if (timer_first) {
                    timer_first->prev = 0;
                }
                else {
                    timer_last = 0;
                }
                timer->running = false;

                timer_btree = timer_btree->btree_get(timer);
                if (timer_first && timer_first->expires == timer->expires) timer_btree = timer_btree->btree_put(timer_first);

                timer->owner->TimerOnTimeout(timer);
            }
        }
        else {
            timer_count += ticks;
            ticks = 0;
        }
    }
    inTimerListRun = false;
}

int  IoMux::TimerNextMs()
{
    if (!timer_first) return -1;
    int d = timer_first->delta - ticks;
    return ((d > 0) ? d : 0);
}

void IoMux::ExecListRun()
{
    UIoExecAnchor runAnchor;
    UIoExec * cur, * contextOwner;
    void * execContext;

    execAnchor.ioExecPrev->ioExecNext = &runAnchor;
    runAnchor.ioExecPrev = execAnchor.ioExecPrev;
    runAnchor.ioExecNext = &execAnchor;
    execAnchor.ioExecPrev = &runAnchor;

    execAnchor.ioExecPrev->ioExecNext = execAnchor.ioExecNext;
    execAnchor.ioExecNext->ioExecPrev = execAnchor.ioExecPrev;
    execAnchor.ioExecPrev = &execAnchor;
    execAnchor.ioExecNext = &execAnchor;

    cur = runAnchor.ioExecNext;
    while (cur != &runAnchor) {
        if (cur->contextOwner == cur) {
            cur->ioExecNext->ioExecPrev = cur->ioExecPrev;
            cur->ioExecPrev->ioExecNext = cur->ioExecNext;
            cur->ioExecNext = cur;
            cur->ioExecPrev = cur;
            cur->IoExec(cur->execContext);
        }
        else {
            contextOwner = cur->contextOwner;
            execContext = cur->execContext;
            delete cur;
            contextOwner->IoExec(execContext);
        }
        cur = runAnchor.ioExecNext;
    }
}

void IoMux::CheckSignals()
{
    if (maintenance.signalSIGHUP) {
        maintenance.signalSIGHUP = false;
        debug->printf("IoMux::CheckSignals SIGHUP");
        this->OnSignal(SIGHUP);
    }
    if (maintenance.signalSIGUSR1) {
        maintenance.signalSIGUSR1 = false;
        debug->printf("IoMux::CheckSignals SIGUSR1");
        this->OnSignal(SIGUSR1);
    }
    if (maintenance.signalShutdown) {
        maintenance.signalShutdown = false;
        debug->printf("IoMux::CheckSignals shutdown");
        this->OnShutdown();
    }
    if (maintenance.signalSIGCHLD) {
        maintenance.signalSIGCHLD = false;
        debug->printf("IoMux::CheckSignals SIGCHLD");
        int status;
        pid_t pid;
        while ((pid = waitpid((pid_t)(-1), &status, WNOHANG | WUNTRACED)) > 0) {
            bool segFault = false;
            if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) {
                segFault = true;
            }
            this->OnChildExit(pid, WTERMSIG(status), WEXITSTATUS(status), segFault);
        }      
    }
}

void IoCancelEpollWaitHelper::IoContextNotify(bool indRX, bool indTX)
{
    byte buf[50];
    bool checkSignal = false;
    int i;

    while (true) {
        int r = read(pipeFdIn, buf, 50);
        if (r == -1) {
            if (errno == EAGAIN) {
                break;
            }

            debug->printf("IoCancelEpollWaitHelper::IoContextNotify read() error (%u : %s)", errno, strerror(errno));
            ASSERT(false, "IoCancelEpollWaitHelper::IoContextNotify read() error");
        }
        else {
            for (i = 0; i < r; i++) {
                checkSignal |= (buf[i] == 1);
            }
        }
    }
    if (checkSignal) {
        IoMux::ioMuxSignalHandler->CheckSignals();
    }
}

/*---------------------------------------------------------------------------*/
/* class IIoMux  		                                                     */
/*---------------------------------------------------------------------------*/
IIoMux::IIoMux()
{
}

IIoMux::~IIoMux()
{
}

/*---------------------------------------------------------------------------*/
/* class UIoContext                                                          */
/*---------------------------------------------------------------------------*/
UIoContext::UIoContext()
{
    this->FDIIoMux = -1;
    this->isError = false;
    this->isHangup = false;
    this->isOpened = false;
}

UIoContext::~UIoContext()
{
}

int UIoContext::GetFd()
{
    return this->FDIIoMux;
}

void UIoContext::SetFd(int fd)
{
    this->FDIIoMux = fd;
}


/*---------------------------------------------------------------------------*/
/* class UIoExec                                                             */
/*---------------------------------------------------------------------------*/
UIoExec::UIoExec()
{
    contextOwner = this;
    contextNext = this;
    contextPrev = this;
    ioExecNext = this;
    ioExecPrev = this;
}

UIoExec::~UIoExec()
{
    if (contextOwner == this) {
        while (contextNext != this) {
            delete contextNext;
        }
    }
    else {
        contextNext->contextPrev = contextPrev;
        contextPrev->contextNext = contextNext;
    }
    ioExecNext->ioExecPrev = ioExecPrev;
    ioExecPrev->ioExecNext = ioExecNext;
}


/*---------------------------------------------------------------------------*/
/* class ITimer																 */
/*---------------------------------------------------------------------------*/
ITimer::ITimer(IIoMux * ioMux, UTimer * owner)
{
    this->ioMux = ioMux;
    this->owner = owner;
    next = 0;
    prev = 0;
    running = false;
}

ITimer::~ITimer()
{
    this->Cancel();
}

void ITimer::Start(unsigned timeoutMs)
{
    ((IoMux *)this->ioMux)->TimerStart(this, timeoutMs);
}

void ITimer::StartLocked(unsigned timeoutMs)
{
    ((IoMux *)this->ioMux)->TimerStartLocked(this, timeoutMs);
}

void ITimer::Cancel()
{
    if (running) {
        ((IoMux *)this->ioMux)->TimerCancel(this);
    }
}

void ITimer::CancelLocked()
{
    if(running) {
        ((IoMux *)this->ioMux)->TimerCancelLocked(this);
    }
}

bool ITimer::IsRunning() const
{
    return this->running;
}

extern "C" dword platform_random()
{
    static bool seeded = false;
    if (!seeded) {
        srand((unsigned int)time(nullptr));
        seeded = true;
    }
    return (rand() ^ (rand() << 16)) & 0x7fffffff;
}
