Skip to content

Commit b3dbccd

Browse files
rustyconoverclaude
andcommitted
feat: add TCP transport (--tcp) to worker
Expose the raw Arrow-IPC TCP transport from vgi-rpc-java (TcpSocketTransport) through the worker: - Worker.runTcp(host, port, idleTimeoutMs) serves via TcpSocketTransport.serveForever and prints the TCP:<host>:<port> launcher discovery line once bound (actual port, so ephemeral port==0 binds are discoverable). - Worker.runFromArgs and the example worker's runWorker accept --tcp [HOST:]PORT (host defaults to 127.0.0.1), sharing Worker.parseTcpAddr. Raw TCP framing carries no auth/encryption (loopback/trusted networks only); HTTP remains the transport for untrusted networks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 545eb5c commit b3dbccd

3 files changed

Lines changed: 66 additions & 1 deletion

File tree

needs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
NO - new release

vgi-example-worker/src/main/java/farm/query/vgi/example/Main.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,13 +1100,15 @@ private static void runWorker(Worker w, String[] args) {
11001100
String host = "127.0.0.1";
11011101
int port = 0;
11021102
String unixSocket = null;
1103+
String tcpAddr = null;
11031104
long idleTimeoutMs = 0;
11041105
for (int i = 0; i < args.length; i++) {
11051106
switch (args[i]) {
11061107
case "--http" -> http = true;
11071108
case "--host" -> host = args[++i];
11081109
case "--port" -> port = Integer.parseInt(args[++i]);
11091110
case "--unix" -> unixSocket = args[++i];
1111+
case "--tcp" -> tcpAddr = args[++i];
11101112
case "--idle-timeout" ->
11111113
idleTimeoutMs = (long) (Double.parseDouble(args[++i]) * 1000.0);
11121114
// Launcher cache-key / fixture-parity flags. The vgi-python
@@ -1123,6 +1125,11 @@ private static void runWorker(Worker w, String[] args) {
11231125
if (unixSocket != null) {
11241126
try { w.runUnixSocket(java.nio.file.Path.of(unixSocket), idleTimeoutMs); }
11251127
catch (Exception e) { e.printStackTrace(); System.exit(1); }
1128+
} else if (tcpAddr != null) {
1129+
try {
1130+
Worker.TcpAddr a = Worker.parseTcpAddr(tcpAddr);
1131+
w.runTcp(a.host(), a.port(), idleTimeoutMs);
1132+
} catch (Exception e) { e.printStackTrace(); System.exit(1); }
11261133
} else if (http) {
11271134
try { w.runHttp(buildHttpConfig(host, port)); }
11281135
catch (Exception e) { e.printStackTrace(); System.exit(1); }

vgi/src/main/java/farm/query/vgi/Worker.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import farm.query.vgirpc.RpcServer;
1414
import farm.query.vgirpc.http.HttpServer;
1515
import farm.query.vgirpc.transport.StdioTransport;
16+
import farm.query.vgirpc.transport.TcpSocketTransport;
1617
import farm.query.vgirpc.transport.UnixSocketTransport;
1718

1819
import java.io.IOException;
@@ -707,6 +708,56 @@ public void runUnixSocket(Path socketPath, long idleTimeoutMs) throws IOExceptio
707708
UnixSocketTransport.serveForever(socketPath, buildServer(false), idleTimeoutMs);
708709
}
709710

711+
/**
712+
* Block accepting TCP connections on {@code host}:{@code port}, dispatching
713+
* each client on a virtual thread, implementing the VGI launcher protocol:
714+
* the worker prints {@code TCP:<host>:<port>\n} to stdout once the listener
715+
* is bound (the actual port, so {@code port == 0} ephemeral binds are
716+
* discoverable), then serves until killed or the idle watchdog fires.
717+
*
718+
* <p>Raw TCP framing carries <strong>no authentication or encryption</strong>
719+
* — bind it to loopback / a trusted network only; use {@link #runHttp} for
720+
* untrusted networks.
721+
*
722+
* @param host bind host ({@code "127.0.0.1"} for loopback)
723+
* @param port bind port; {@code 0} selects a free port
724+
* @param idleTimeoutMs idle watchdog in milliseconds; {@code <= 0} disables it
725+
* @throws IOException if the socket cannot be bound or served
726+
*/
727+
public void runTcp(String host, int port, long idleTimeoutMs) throws IOException {
728+
TcpSocketTransport.serveForever(host, port, buildServer(false), idleTimeoutMs,
729+
(boundHost, boundPort) -> {
730+
System.out.println("TCP:" + boundHost + ":" + boundPort);
731+
System.out.flush();
732+
});
733+
}
734+
735+
/**
736+
* Parsed {@code [HOST:]PORT} TCP bind spec. Host defaults to loopback.
737+
*
738+
* @param host bind host
739+
* @param port bind port ({@code 0} = ephemeral)
740+
*/
741+
public record TcpAddr(String host, int port) {}
742+
743+
/**
744+
* Parse a {@code [HOST:]PORT} TCP bind spec as accepted by {@code --tcp}.
745+
* A bare {@code PORT} binds {@code 127.0.0.1}; an empty host (leading
746+
* {@code ":"}) also defaults to loopback.
747+
*
748+
* @param spec the {@code [HOST:]PORT} string
749+
* @return the parsed host/port
750+
*/
751+
public static TcpAddr parseTcpAddr(String spec) {
752+
int idx = spec.lastIndexOf(':');
753+
if (idx >= 0) {
754+
String h = spec.substring(0, idx);
755+
return new TcpAddr(h.isEmpty() ? "127.0.0.1" : h,
756+
Integer.parseInt(spec.substring(idx + 1)));
757+
}
758+
return new TcpAddr("127.0.0.1", Integer.parseInt(spec));
759+
}
760+
710761
/**
711762
* Run as an HTTP server bound to {@code host}/{@code port}, blocking until shutdown.
712763
*
@@ -723,8 +774,9 @@ public void runHttp(String host, int port) throws Exception {
723774
* the four flags every VGI worker accepts and runs the matching transport:
724775
* <ul>
725776
* <li>{@code --unix <path>}: AF_UNIX socket (launcher protocol)
777+
* <li>{@code --tcp [<host>:]<port>}: TCP socket (launcher protocol)
726778
* <li>{@code --http} with optional {@code --host}, {@code --port}: HTTP
727-
* <li>{@code --idle-timeout <seconds>}: passed to {@code runUnixSocket}
779+
* <li>{@code --idle-timeout <seconds>}: passed to {@code runUnixSocket} / {@code runTcp}
728780
* <li>(default): stdio
729781
* </ul>
730782
* Also honours {@code VGI_WORKER_STDERR}: redirects {@link System#err} to
@@ -752,13 +804,15 @@ public void runFromArgs(String[] args,
752804
String host = "127.0.0.1";
753805
int port = 0;
754806
String unixSocket = null;
807+
String tcpAddr = null;
755808
long idleTimeoutMs = 0;
756809
for (int i = 0; i < args.length; i++) {
757810
switch (args[i]) {
758811
case "--http" -> http = true;
759812
case "--host" -> host = args[++i];
760813
case "--port" -> port = Integer.parseInt(args[++i]);
761814
case "--unix" -> unixSocket = args[++i];
815+
case "--tcp" -> tcpAddr = args[++i];
762816
case "--idle-timeout" -> idleTimeoutMs =
763817
(long) (Double.parseDouble(args[++i]) * 1000.0);
764818
default -> { System.err.println("unknown arg: " + args[i]); System.exit(2); }
@@ -767,6 +821,9 @@ public void runFromArgs(String[] args,
767821
try {
768822
if (unixSocket != null) {
769823
runUnixSocket(Path.of(unixSocket), idleTimeoutMs);
824+
} else if (tcpAddr != null) {
825+
TcpAddr a = parseTcpAddr(tcpAddr);
826+
runTcp(a.host(), a.port(), idleTimeoutMs);
770827
} else if (http) {
771828
HttpServer.Config.Builder b = HttpServer.Config.builder().host(host).port(port);
772829
if (httpCustomizer != null) b = httpCustomizer.apply(b);

0 commit comments

Comments
 (0)