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

com.questdb.net.Win32SelectDispatcher Maven / Gradle / Ivy

There is a newer version: 3.3.3
Show newest version
/*******************************************************************************
 *    ___                  _   ____  ____
 *   / _ \ _   _  ___  ___| |_|  _ \| __ )
 *  | | | | | | |/ _ \/ __| __| | | |  _ \
 *  | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *   \__\_\\__,_|\___||___/\__|____/|____/
 *
 * Copyright (C) 2014-2016 Appsicle
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 *
 ******************************************************************************/

package com.questdb.net;

import com.questdb.ex.NetworkError;
import com.questdb.iter.clock.Clock;
import com.questdb.log.Log;
import com.questdb.log.LogFactory;
import com.questdb.misc.*;
import com.questdb.mp.*;
import com.questdb.std.LongIntHashMap;
import com.questdb.std.LongMatrix;
import com.questdb.std.ObjectFactory;

import java.io.IOException;

public class Win32SelectDispatcher extends SynchronizedJob implements Dispatcher {

    private static final int M_TIMESTAMP = 0;
    private static final int M_FD = 1;
    private static final int M_OPERATION = 2;
    private static final Log LOG = LogFactory.getLog(Win32SelectDispatcher.class);
    private static final int COUNT_OFFSET;
    private static final int ARRAY_OFFSET;
    private static final int FD_READ = 1;
    private static final int FD_WRITE = 2;
    private final FDSet readFdSet;
    private final FDSet writeFdSet;
    private final long socketFd;
    private final RingQueue> ioQueue;
    private final Sequence ioSequence;
    private final RingQueue> interestQueue;
    private final MPSequence interestPubSequence;
    private final SCSequence interestSubSequence = new SCSequence();
    private final Clock clock;
    private final int timeout;
    private final LongMatrix pending = new LongMatrix<>(4);
    private final int maxConnections;
    private final LongIntHashMap fds = new LongIntHashMap();
    private final ContextFactory contextFactory;
    private int connectionCount = 0;

    public Win32SelectDispatcher(
            CharSequence ip,
            int port,
            int maxConnections,
            int timeout,
            RingQueue> ioQueue,
            Sequence ioSequence,
            Clock clock,
            int capacity,
            ObjectFactory> eventFactory,
            ContextFactory contextFactory
    ) {
        this.readFdSet = new FDSet(capacity);
        this.writeFdSet = new FDSet(capacity);
        this.ioQueue = ioQueue;
        this.ioSequence = ioSequence;
        this.interestQueue = new RingQueue<>(eventFactory, ioQueue.getCapacity());
        this.interestPubSequence = new MPSequence(interestQueue.getCapacity());
        this.interestPubSequence.then(this.interestSubSequence).then(this.interestPubSequence);
        this.clock = clock;
        this.maxConnections = maxConnections;
        this.timeout = timeout;
        this.contextFactory = contextFactory;

        // bind socket
        this.socketFd = Net.socketTcp(false);
        if (Net.bind(this.socketFd, ip, port)) {
            Net.listen(this.socketFd, 128);
            int r = pending.addRow();
            pending.set(r, M_TIMESTAMP, System.currentTimeMillis());
            pending.set(r, M_FD, socketFd);
            pending.set(r, M_OPERATION, ChannelStatus.READ);
            readFdSet.add(socketFd);
            readFdSet.setCount(1);
            writeFdSet.setCount(0);
        } else {
            throw new NetworkError("Failed to bind socket. System error " + Os.errno());
        }
    }

    @Override
    public void close() throws IOException {
        readFdSet.close();
        writeFdSet.close();

        for (int i = 0, n = pending.size(); i < n; i++) {
            Files.close(pending.get(i, M_FD));
            Misc.free(pending.get(i));
        }

        pending.zapTop(pending.size());
    }

    @Override
    public int getConnectionCount() {
        return connectionCount;
    }

    @Override
    public void registerChannel(C context, int channelStatus) {
        long cursor = interestPubSequence.nextBully();
        Event evt = interestQueue.get(cursor);
        evt.context = context;
        evt.channelStatus = channelStatus;
        LOG.debug().$("Re-queuing ").$(channelStatus).$(" on ").$(context.getFd()).$();
        interestPubSequence.done(cursor);
    }

    private static native int select(long readfds, long writefds, long exceptfds);

    private static native int countOffset();

    private static native int arrayOffset();

    private void accept(long timestamp) {
        while (true) {
            long _fd = Net.accept(socketFd);

            if (_fd < 0) {
                int err = Os.errno();
                if (err != Net.EWOULDBLOCK && err != 0) {
                    LOG.error().$("Error in accept(): ").$(err).$();
                }
                break;
            }

            LOG.info().$(" Connected ").$(_fd).$();

            if (Net.configureNonBlocking(_fd) < 0) {
                LOG.error().$("Cannot make FD non-blocking").$();
                Files.close(_fd);
                continue;
            }

            connectionCount++;

            if (connectionCount > maxConnections) {
                LOG.info().$("Too many connections, kicking out ").$(_fd).$();
                Files.close(_fd);
                connectionCount--;
                return;
            }

            addPending(_fd, timestamp);
        }
    }

    private void addPending(long _fd, long timestamp) {
        int r = pending.addRow();
        LOG.debug().$(" Matrix row ").$(r).$(" for ").$(_fd).$();
        pending.set(r, M_TIMESTAMP, timestamp);
        pending.set(r, M_FD, _fd);
        pending.set(r, M_OPERATION, ChannelStatus.READ);
        pending.set(r, contextFactory.newInstance(_fd, clock));
    }

    private void disconnect(C context, int disconnectReason) {
        LOG.info().$("Disconnected ").$(context.getFd()).$(": ").$(DisconnectReason.nameOf(disconnectReason)).$();
        context.close();
        connectionCount--;
    }

    private void enqueue(C context, int channelStatus) {
        long cursor = ioSequence.nextBully();
        Event evt = ioQueue.get(cursor);
        evt.context = context;
        evt.channelStatus = channelStatus;
        ioSequence.done(cursor);
        LOG.debug().$("Queuing ").$(channelStatus).$(" on ").$(context.getFd()).$();

    }

    private boolean processRegistrations(long timestamp) {
        long cursor;
        boolean useful = false;
        while ((cursor = interestSubSequence.next()) > -1) {
            useful = true;
            Event evt = interestQueue.get(cursor);
            C context = evt.context;
            int channelStatus = evt.channelStatus;
            interestSubSequence.done(cursor);

            int r = pending.addRow();
            pending.set(r, M_TIMESTAMP, timestamp);
            pending.set(r, M_FD, context.getFd());
            pending.set(r, M_OPERATION, channelStatus);
            pending.set(r, context);
        }

        return useful;
    }

    private void queryFdSets(long timestamp) {
        for (int i = 0, n = readFdSet.getCount(); i < n; i++) {
            long fd = readFdSet.get(i);

            if (fd == socketFd) {
                accept(timestamp);
            } else {
                fds.put(fd, FD_READ);
            }
        }

        // collect writes into hash map
        for (int i = 0, n = writeFdSet.getCount(); i < n; i++) {
            long fd = writeFdSet.get(i);
            int op = fds.get(fd);
            if (op == -1) {
                fds.put(fd, FD_WRITE);
            } else {
                fds.put(fd, FD_READ | FD_WRITE);
            }
        }
    }

    @Override
    protected boolean runSerially() {
        int count = select(readFdSet.address, writeFdSet.address, 0);
        if (count < 0) {
            LOG.error().$("Error in select(): ").$(Os.errno()).$();
            return false;
        }

        final long timestamp = System.currentTimeMillis();
        boolean useful = false;
        fds.clear();

        // collect reads into hash map
        if (count > 0) {
            queryFdSets(timestamp);
            useful = true;
        }

        // process returned fds
        useful = processRegistrations(timestamp) | useful;

        // re-arm select() fds
        int readFdCount = 0;
        int writeFdCount = 0;
        readFdSet.reset();
        writeFdSet.reset();
        long deadline = timestamp - timeout;
        for (int i = 0, n = pending.size(); i < n; ) {
            long ts = pending.get(i, M_TIMESTAMP);
            long fd = pending.get(i, M_FD);
            int _new_op = fds.get(fd);

            if (_new_op == -1) {

                // check if expired
                if (ts < deadline && fd != socketFd) {
                    disconnect(pending.get(i), DisconnectReason.IDLE);
                    pending.deleteRow(i);
                    n--;
                    useful = true;
                    continue;
                }

                // not fired, simply re-arm
                switch ((int) pending.get(i, M_OPERATION)) {
                    case ChannelStatus.READ:
                        readFdSet.add(fd);
                        readFdCount++;
                        i++;
                        break;
                    case ChannelStatus.WRITE:
                        writeFdSet.add(fd);
                        writeFdCount++;
                        i++;
                        break;
                    case ChannelStatus.DISCONNECTED:
                        disconnect(pending.get(i), DisconnectReason.SILLY);
                        pending.deleteRow(i);
                        n--;
                        useful = true;
                        break;
                    case ChannelStatus.EOF:
                        disconnect(pending.get(i), DisconnectReason.PEER);
                        pending.deleteRow(i);
                        n--;
                        useful = true;
                        break;
                    default:
                        break;
                }
            } else {
                // this fd just has fired
                // publish event
                // and remove from pending
                final C context = pending.get(i);

                if ((_new_op & FD_READ) > 0) {
                    enqueue(context, ChannelStatus.READ);
                }

                if ((_new_op & FD_WRITE) > 0) {
                    enqueue(context, ChannelStatus.WRITE);
                }
                pending.deleteRow(i);
                n--;
            }
        }

        readFdSet.setCount(readFdCount);
        writeFdSet.setCount(writeFdCount);
        return useful;
    }

    private static class FDSet {
        private long address;
        private int size;
        private long _wptr;
        private long lim;

        private FDSet(int size) {
            int l = ARRAY_OFFSET + 8 * size;
            this.address = Unsafe.malloc(l);
            this.size = size;
            this._wptr = address + ARRAY_OFFSET;
            this.lim = address + l;
        }

        private void add(long fd) {
            if (_wptr == lim) {
                resize();
            }
            long p = _wptr;
            Unsafe.getUnsafe().putLong(p, fd);
            _wptr = p + 8;
        }

        private void close() {
            if (address != 0) {
                Unsafe.free(address, lim - address);
                address = 0;
            }
        }

        private long get(int index) {
            return Unsafe.getUnsafe().getLong(address + ARRAY_OFFSET + index * 8L);
        }

        private int getCount() {
            return Unsafe.getUnsafe().getInt(address + COUNT_OFFSET);
        }

        private void setCount(int count) {
            Unsafe.getUnsafe().putInt(address + COUNT_OFFSET, count);
        }

        private void reset() {
            _wptr = address + ARRAY_OFFSET;
        }

        private void resize() {
            int sz = size * 2;
            int l = ARRAY_OFFSET + 8 * sz;
            long _addr = Unsafe.malloc(l);
            Unsafe.getUnsafe().copyMemory(address, _addr, lim - address);
            Unsafe.free(address, lim - address);
            lim = _addr + l;
            size = sz;
            _wptr = _addr + (_wptr - address);
            address = _addr;
        }
    }

    static {
        Os.init();

        ARRAY_OFFSET = arrayOffset();
        COUNT_OFFSET = countOffset();
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy