// 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