// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.

package net.avadeaux.klipspringer;

import java.io.*;
import java.net.*;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedList;

/**
 * Asynchronously services clients with a command interface. Maintains its own thread pool.
 */
public class AsyncCommandServer implements Runnable {
    private final CommandInterface cmdExec;
    private final ServerSocketChannel serv;
    private final LinkedList<Runner> pool = new LinkedList<Runner>();
    private final int threadLimit;
    private int threadsUsed = 0, threadsCreated = 0;

    private class Runner implements Runnable {
        private SocketChannel sc = null;

        synchronized void runon(SocketChannel sc) {
            this.sc = sc;
            notifyAll();
        }

        public synchronized void run() {
            while (true) {
                while (sc == null) {
                    try { wait(); } catch (InterruptedException ex) { }
                }
                try {
                    BufferedReader r = new BufferedReader(Channels.newReader(sc, StandardCharsets.UTF_8));
                    String input = r.readLine();
                    if (input != null) { cmdExec.process(Query.parse(input.trim()), sc); }
                } catch (IOException ex) {
                    System.err.println("I/O error on interface connection: "+ex);
                    ex.printStackTrace();
                } catch (Throwable thr) {
                    System.err.println("Throw on interface connection: "+thr);
                    thr.printStackTrace();
                }
                sc = null;
                returnRunner(this);
            }
        }
    }

    /** Sets up a server to receive commands on the given port. Expects a line at a time from the
      * client, and parses each line as a URL query. Depending on the command, a response may be
      * written to the socket. */
    public AsyncCommandServer(CommandInterface cmdExec, Path path, int threadLimit) throws IOException {
        this.cmdExec = cmdExec;
        this.threadLimit = threadLimit;
        Files.deleteIfExists(path);
        Files.createDirectories(path.getParent());
        serv = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
        serv.bind(UnixDomainSocketAddress.of(path));
        path.toFile().deleteOnExit();
    }

    private synchronized Runner getRunner() {
        while (threadsUsed == threadLimit) {
            try { wait(); } catch (InterruptedException ex) { }
        }
        threadsUsed++;
        if (pool.size() > 0) {
            return pool.remove();
        } else {
            Runner runner = new Runner();
            new Thread(runner, "AsyncCommandServer.Runner-" + ++threadsCreated).start();
            return runner;
        }
    }

    private synchronized void returnRunner(Runner runner) {
        pool.add(runner);
        threadsUsed--;
        notifyAll();
    }

    /** Main loop that accepts requests and sends them off to threads. The loop is intended to be
      * run in its own thread. */
    public void run() {
        try {
            while (true) {
                SocketChannel sc = serv.accept();
                Runner runner = getRunner();
                runner.runon(sc);
            }
        } catch (IOException ex) {
            System.err.println("Track info server failure: " + ex);
        }
    }
}

Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home