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

net.openhft.chronicle.wire.channel.ChronicleServiceMain Maven / Gradle / Ivy

/*
 * Copyright 2016-2022 chronicle.software
 *
 *       https://chronicle.software
 *
 * 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 net.openhft.chronicle.wire.channel;

import net.openhft.affinity.AffinityLock;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.io.Closeable;
import net.openhft.chronicle.core.io.ClosedIORuntimeException;
import net.openhft.chronicle.core.io.InvalidMarshallableException;
import net.openhft.chronicle.threads.NamedThreadFactory;
import net.openhft.chronicle.threads.Pauser;
import net.openhft.chronicle.wire.Marshallable;
import net.openhft.chronicle.wire.SelfDescribingMarshallable;
import net.openhft.chronicle.wire.Wires;
import net.openhft.chronicle.wire.channel.impl.BufferedChronicleChannel;
import net.openhft.chronicle.wire.channel.impl.TCPChronicleChannel;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * Represents the main class for the Chronicle Service which is responsible for
 * accepting and managing incoming connections using multiple threads.
 */
@SuppressWarnings("deprecation")
public class ChronicleServiceMain extends SelfDescribingMarshallable implements Closeable {
    int port;  // The port on which the server listens for incoming connections
    Marshallable microservice;  // The microservice configuration or definition (not used in the provided code)
    boolean buffered;  // Flag to determine whether the service should use buffered channels
    transient ServerSocketChannel ssc;  // Server socket channel for accepting incoming connections
    transient volatile boolean closed;  // Flag to track whether the service is closed
    transient Set channels;  // A set of active channels managed by this service

    /**
     * The main method acts as the entry point to start the ChronicleServiceMain.
     * It creates a new instance of the class from a specified configuration file and runs it.
     *
     * @param args Command line arguments. The first argument is expected to be the path to the configuration file.
     * @throws IOException                  if there's an I/O issue
     * @throws InvalidMarshallableException if there's an issue with marshalling
     */
    public static void main(String... args) throws IOException, InvalidMarshallableException {
        ChronicleServiceMain main = Marshallable.fromFile(ChronicleServiceMain.class, args[0]);
        main.buffered = Jvm.getBoolean("buffered", main.buffered);
        main.run();
    }

    /**
     * Starts the service by opening a server socket channel on the specified port and
     * managing incoming connections using multiple threads.
     * Each incoming connection is handled by a separate thread using a `ConnectionHandler`.
     */
    void run() {
        channels = Collections.newSetFromMap(new WeakHashMap<>());  // Initialize the set of channels using a weak hash map

        Jvm.startup().on(getClass(), "Starting " + this);
        Thread.currentThread().setName("acceptor");
        ExecutorService service = Executors.newCachedThreadPool(new NamedThreadFactory("connections"));  // Thread pool to manage incoming connections
        try {
            ssc = ServerSocketChannel.open();
            ssc.bind(new InetSocketAddress(port));
            ChronicleChannelCfg channelCfg = new ChronicleChannelCfg<>().addHostnamePort(null, port);
            Function redirectFunction = this::replaceOutHeader;
            while (!isClosed()) {
                final SocketChannel sc = ssc.accept();
                sc.socket().setTcpNoDelay(true);
                final TCPChronicleChannel connection0 = new TCPChronicleChannel(SystemContext.INSTANCE, channelCfg, sc, h -> h, redirectFunction);
                ChronicleChannel channel = buffered ? new BufferedChronicleChannel(connection0, Pauser.balanced()) : connection0;
                channels.add(channel);
                service.submit(() -> new ConnectionHandler(channel).run());
            }
        } catch (Throwable e) {
            if (!isClosed()) Jvm.error().on(getClass(), e);  // Log any exceptions if the service isn't closed
        } finally {
            close();
            Jvm.pause(100);
            // Synchronize on Wires class to avoid concurrent modifications while shutting down
            synchronized (Wires.class) {
                AffinityLock.dumpLocks();  // Dump affinity locks for debugging
                service.shutdownNow();  // Shutdown the thread pool
            }
            try {
                service.awaitTermination(1, TimeUnit.SECONDS);  // Wait for termination of the thread pool
            } catch (InterruptedException e) {
                Jvm.warn().on(getClass(), e);
                Thread.currentThread().interrupt();  // Set the interrupt flag if interrupted
            }
        }
    }

    /**
     * Replaces the outbound header for the channel based on its type.
     *
     * @param channelHandler the current channel header
     * @return a new channel header, either an OkHeader or a RedirectHeader
     */
    protected ChannelHeader replaceOutHeader(ChannelHeader channelHandler) {
        if (channelHandler instanceof OkHeader)
            return new OkHeader();
        //noinspection unchecked
        return new RedirectHeader(Collections.emptyList());
    }

    @Override
    public void close() {
        closed = true;
        Closeable.closeQuietly(ssc);
        Closeable.closeQuietly(channels);
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    /**
     * Inner class to represent the connection handler.
     * This class is responsible for handling individual incoming connections and processing
     * their associated messages/events using the service's microservice logic.
     */
    class ConnectionHandler {
        final ChronicleChannel channel;  // The channel associated with this connection

        /**
         * Constructor to initialize the handler with a specific channel.
         *
         * @param channel the channel to be managed by this handler
         */
        public ConnectionHandler(ChronicleChannel channel) {
            this.channel = channel;
        }

        /**
         * Runs the connection handler logic.
         * This method manages the lifecycle of the channel's connection, processes messages/events
         * using the service's microservice logic, and handles various exceptions.
         */
        @SuppressWarnings("try")
        void run() {
            try {
                Jvm.debug().on(ChronicleServiceMain.class, "Server got " + channel.headerIn());

                // Deep copy of the main microservice instance
                final Marshallable microservice = ChronicleServiceMain.this.microservice.deepCopy();

                // Reflection to get the 'out' field from the microservice class
                final Field field = Jvm.getFieldOrNull(microservice.getClass(), "out");
                if (field == null)
                    throw new IllegalStateException("Microservice " + microservice + " must have a field called out");

                // Obtain a method writer proxy of the appropriate type for this channel
                Object out = channel.methodWriter(field.getType());

                // The AffinityLock is used here to bind the executing thread to a CPU core
                try (AffinityLock lock = AffinityLock.acquireLock()) {
                    field.set(microservice, out);

                    // Run the microservice's event handler logic
                    channel.eventHandlerAsRunnable(microservice).run();

                } catch (ClosedIORuntimeException e) {
                    Thread.yield();
                    if (!((Closeable) microservice).isClosed())
                        Jvm.debug().on(getClass(), "readOne threw " + e);

                } catch (Exception e) {
                    Thread.yield();
                    if (!((Closeable) microservice).isClosed() && !channel.isClosed())
                        Jvm.warn().on(getClass(), "readOne threw ", e);
                }
            } catch (Throwable t) {
                Jvm.error().on(getClass(), t);

            } finally {
                Closeable.closeQuietly(channel);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy