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

io.questdb.network.IODispatcherWindows Maven / Gradle / Ivy

/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2024 QuestDB
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

package io.questdb.network;

import io.questdb.std.LongIntHashMap;

public class IODispatcherWindows> extends AbstractIODispatcher {
    private final LongIntHashMap fds = new LongIntHashMap();
    private final FDSet readFdSet;
    private final SelectFacade sf;
    private final FDSet writeFdSet;
    // used for heartbeats
    private long idSeq = 1;
    private boolean listenerRegistered;

    public IODispatcherWindows(
            IODispatcherConfiguration configuration,
            IOContextFactory ioContextFactory
    ) {
        super(configuration, ioContextFactory);
        this.sf = configuration.getSelectFacade();
        this.readFdSet = new FDSet(configuration.getEventCapacity());
        this.writeFdSet = new FDSet(configuration.getEventCapacity());
        readFdSet.add(serverFd);
        readFdSet.setCount(1);
        writeFdSet.setCount(0);
        listenerRegistered = true;
    }

    @Override
    public void close() {
        super.close();
        readFdSet.close();
        writeFdSet.close();
        LOG.info().$("closed").$();
    }

    private long nextOpId() {
        return idSeq++;
    }

    private boolean processRegistrations(long timestamp) {
        long cursor;
        boolean useful = false;
        while ((cursor = interestSubSeq.next()) > -1) {
            final IOEvent evt = interestQueue.get(cursor);
            final C context = evt.context;
            int operation = evt.operation;
            final long srcOpId = context.getAndResetHeartbeatId();

            final long opId = nextOpId();
            final long fd = context.getFd();

            interestSubSeq.done(cursor);
            if (operation == IOOperation.HEARTBEAT) {
                assert srcOpId != -1;

                int heartbeatRow = pendingHeartbeats.binarySearch(srcOpId, OPM_ID);
                if (heartbeatRow < 0) {
                    continue; // The connection is already closed.
                } else {
                    operation = (int) pendingHeartbeats.get(heartbeatRow, OPM_OPERATION);

                    LOG.debug().$("processing heartbeat registration [fd=").$(fd)
                            .$(", op=").$(operation)
                            .$(", srcId=").$(srcOpId)
                            .$(", id=").$(opId).I$();

                    int r = pending.addRow();
                    pending.set(r, OPM_CREATE_TIMESTAMP, pendingHeartbeats.get(heartbeatRow, OPM_CREATE_TIMESTAMP));
                    pending.set(r, OPM_HEARTBEAT_TIMESTAMP, timestamp);
                    pending.set(r, OPM_FD, fd);
                    pending.set(r, OPM_ID, opId);
                    pending.set(r, OPM_OPERATION, operation);
                    pending.set(r, context);

                    pendingHeartbeats.deleteRow(heartbeatRow);
                }
            } else {
                if (operation == IOOperation.READ && context.getSocket().isMorePlaintextBuffered()) {
                    publishOperation(IOOperation.READ, context);
                    continue;
                }

                LOG.debug().$("processing registration [fd=").$(fd)
                        .$(", op=").$(operation)
                        .$(", id=").$(opId).I$();

                int r = pending.addRow();
                pending.set(r, OPM_CREATE_TIMESTAMP, timestamp);
                pending.set(r, OPM_HEARTBEAT_TIMESTAMP, timestamp);
                pending.set(r, OPM_FD, context.getFd());
                pending.set(r, OPM_ID, opId);
                pending.set(r, OPM_OPERATION, operation);
                pending.set(r, context);
            }
            useful = true;
        }
        return useful;
    }

    private void queryFdSets(long timestamp) {
        // collect reads into hash map
        for (int i = 0, n = readFdSet.getCount(); i < n; i++) {
            final long fd = readFdSet.get(i);
            if (fd == serverFd) {
                accept(timestamp);
            } else {
                fds.put(fd, SelectAccessor.FD_READ);
            }
        }

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

    @Override
    protected void pendingAdded(int index) {
        pending.set(index, OPM_OPERATION, initialBias == IODispatcherConfiguration.BIAS_READ ? IOOperation.READ : IOOperation.WRITE);
    }

    @Override
    protected void registerListenerFd() {
        listenerRegistered = true;
    }

    @Override
    protected boolean runSerially() {
        final long timestamp = clock.getTicks();
        processDisconnects(timestamp);

        int count;
        if (readFdSet.getCount() > 0 || writeFdSet.getCount() > 0) {
            count = sf.select(readFdSet.address(), writeFdSet.address(), 0, 0);
            if (count < 0) {
                LOG.error().$("select failure [err=").$(nf.errno()).I$();
                return false;
            }
        } else {
            count = 0;
        }

        boolean useful = false;
        fds.clear();
        int watermark = pending.size();
        // collect reads into hash map
        if (count > 0) {
            queryFdSets(timestamp);
            useful = true;
        }

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

        // re-arm select() fds
        int readFdCount = 0;
        int writeFdCount = 0;
        readFdSet.clear();
        writeFdSet.clear();
        long deadline = timestamp - idleConnectionTimeout;
        final long heartbeatTimestamp = timestamp - heartbeatIntervalMs;
        for (int i = 0, n = pending.size(); i < n; ) {
            final C context = pending.get(i);

            // check if the context is waiting for a suspend event
            final SuspendEvent suspendEvent = context.getSuspendEvent();
            if (suspendEvent != null) {
                if (suspendEvent.checkTriggered() || suspendEvent.isDeadlineMet(timestamp)) {
                    // the event has been triggered or expired already, clear it and proceed
                    context.clearSuspendEvent();
                }
            }

            final long fd = pending.get(i, OPM_FD);
            final int newOp = fds.get(fd);
            assert fd != serverFd;

            if (newOp == -1) {
                // new operation case

                // check if the connection was idle for too long
                if (pending.get(i, OPM_CREATE_TIMESTAMP) < deadline) {
                    doDisconnect(context, DISCONNECT_SRC_IDLE);
                    pending.deleteRow(i);
                    n--;
                    if (i < watermark) {
                        watermark--;
                    }
                    useful = true;
                    continue;
                }

                // check if we have heartbeats to be sent
                if (i < watermark && pending.get(i, OPM_HEARTBEAT_TIMESTAMP) < heartbeatTimestamp) {
                    final long opId = pending.get(i, OPM_ID);
                    context.setHeartbeatId(opId);
                    publishOperation(IOOperation.HEARTBEAT, context);

                    int r = pendingHeartbeats.addRow();
                    pendingHeartbeats.set(r, OPM_CREATE_TIMESTAMP, pending.get(i, OPM_CREATE_TIMESTAMP));
                    pendingHeartbeats.set(r, OPM_FD, fd);
                    pendingHeartbeats.set(r, OPM_ID, opId);
                    pendingHeartbeats.set(r, OPM_OPERATION, pending.get(i, OPM_OPERATION));
                    pendingHeartbeats.set(r, context);

                    pending.deleteRow(i);
                    n--;
                    watermark--;
                    useful = true;
                } else {
                    int operation = (int) pending.get(i, OPM_OPERATION);
                    i++;
                    if (suspendEvent != null) {
                        // if the operation was suspended, we request a read to be able to detect a client disconnect
                        operation = IOOperation.READ;
                    }

                    if (operation == IOOperation.READ || context.getSocket().wantsTlsRead()) {
                        readFdSet.add(fd);
                        readFdCount++;
                    }
                    if (operation == IOOperation.WRITE || context.getSocket().wantsTlsWrite()) {
                        writeFdSet.add(fd);
                        writeFdCount++;
                    }
                }
            } else {
                // select()'ed operation case
                if (suspendEvent != null) {
                    // the event is still pending, check if we have a client disconnect
                    if (testConnection(context.getFd())) {
                        doDisconnect(context, DISCONNECT_SRC_PEER_DISCONNECT);
                        pending.deleteRow(i);
                        n--;
                        watermark--;
                    } else {
                        // the connection is alive, so we need to add it to poll to be able to detect broken connection
                        readFdSet.add(fd);
                        readFdCount++;

                        if (context.getSocket().wantsTlsWrite()) {
                            writeFdSet.add(fd);
                            writeFdCount++;
                        }
                        i++; // now skip to the next operation
                    }
                    continue;
                }

                // we got a (potentially requested) event
                useful = true;

                final int requestedOp = (int) pending.get(i, OPM_OPERATION);
                final boolean readyForWrite = (newOp & SelectAccessor.FD_WRITE) > 0;
                final boolean readyForRead = (newOp & SelectAccessor.FD_READ) > 0;

                if ((requestedOp == IOOperation.WRITE && readyForWrite) || (requestedOp == IOOperation.READ && readyForRead)) {
                    // If the socket is also ready for another operation type, do it.
                    if (context.getSocket().tlsIO(tlsIOFlags(requestedOp, readyForRead, readyForWrite)) < 0) {
                        doDisconnect(context, DISCONNECT_SRC_TLS_ERROR);
                        pending.deleteRow(i);
                        n--;
                        watermark--;
                        continue;
                    }
                    // publish event and remove from pending
                    publishOperation(requestedOp, context);
                    pending.deleteRow(i);
                    n--;
                    watermark--;
                } else {
                    // It's something different from the requested operation.
                    if (context.getSocket().tlsIO(tlsIOFlags(readyForRead, readyForWrite)) < 0) {
                        doDisconnect(context, DISCONNECT_SRC_TLS_ERROR);
                        pending.deleteRow(i);
                        n--;
                        watermark--;
                        continue;
                    }
                    // Now we need to re-arm poll.
                    if (requestedOp == IOOperation.READ || context.getSocket().wantsTlsRead()) {
                        readFdSet.add(fd);
                        readFdCount++;
                    }
                    if (requestedOp == IOOperation.WRITE || context.getSocket().wantsTlsWrite()) {
                        writeFdSet.add(fd);
                        writeFdCount++;
                    }
                    i++; // now skip to the next operation
                }
            }
        }

        if (listenerRegistered) {
            assert serverFd >= 0;
            readFdSet.add(serverFd);
            readFdCount++;
        }

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

    @Override
    protected void unregisterListenerFd() {
        listenerRegistered = false;
    }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy