Skip to content

Commit dce0dcd

Browse files
committed
fix: add local endpoint detection to UnoServerPool and skip demand file updates for remote-only configurations
1 parent 300b8a9 commit dce0dcd

4 files changed

Lines changed: 71 additions & 43 deletions

File tree

app/common/src/main/java/stirling/software/common/util/ProcessExecutor.java

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import java.util.concurrent.Semaphore;
1717
import java.util.concurrent.TimeUnit;
1818
import java.util.concurrent.TimeoutException;
19-
import java.util.concurrent.locks.LockSupport;
2019

2120
import io.github.pixee.security.BoundedLineReader;
2221

@@ -548,57 +547,22 @@ private void validateCommand(List<String> command) {
548547

549548
/**
550549
* Signal the on-demand unoserver manager that a conversion is needed. Writes the current epoch
551-
* timestamp to /tmp/uno-last-used. The demand manager watches this file and:
550+
* timestamp to /tmp/uno-last-used. The demand manager watches this file. Skips writing when
551+
* configured purely with remoteunoserver endpoints.
552552
*/
553553
private static void signalUnoServerDemand() {
554+
if (unoServerPool != null && !unoServerPool.hasLocalEndpoints()) {
555+
return;
556+
}
554557
try {
555558
Path demandFile = Path.of("/tmp/uno-last-used");
556559
String epoch = String.valueOf(System.currentTimeMillis() / 1000);
557560
Files.writeString(demandFile, epoch);
558-
559-
// waitForUnoServerReady(30);
560561
} catch (IOException e) {
561562
log.debug("Could not write unoserver demand file: {}", e.getMessage());
562563
}
563564
}
564565

565-
/**
566-
* Wait for at least one unoserver endpoint to accept connections. Uses a simple TCP connect
567-
* probe to the first configured endpoint.
568-
*/
569-
private static void waitForUnoServerReady(int maxWaitSeconds) {
570-
if (unoServerPool == null || unoServerPool.isEmpty()) {
571-
return;
572-
}
573-
// Try a quick TCP probe to the first endpoint
574-
try {
575-
var lease = unoServerPool.acquireEndpoint(1, TimeUnit.MILLISECONDS);
576-
var endpoint = lease.getEndpoint();
577-
lease.close();
578-
579-
String host = endpoint.getHost();
580-
int port = endpoint.getPort();
581-
if (host == null || host.isBlank()) host = "127.0.0.1";
582-
if (port <= 0) port = 2003;
583-
584-
for (int i = 0; i < maxWaitSeconds; i++) {
585-
try (var socket = new java.net.Socket()) {
586-
socket.connect(new java.net.InetSocketAddress(host, port), 1000);
587-
log.debug("unoserver ready on {}:{}", host, port);
588-
return;
589-
} catch (IOException e) {
590-
// Not ready yet, wait
591-
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
592-
}
593-
}
594-
log.warn("unoserver not ready after {}s, proceeding anyway", maxWaitSeconds);
595-
} catch (InterruptedException e) {
596-
Thread.currentThread().interrupt();
597-
} catch (TimeoutException e) {
598-
// Pool fully occupied, unoserver is likely running
599-
}
600-
}
601-
602566
public enum Processes {
603567
LIBRE_OFFICE,
604568
PDFTOHTML,

app/common/src/main/java/stirling/software/common/util/UnoServerPool.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,38 @@ public boolean isEmpty() {
3535
return endpoints.isEmpty();
3636
}
3737

38+
public boolean hasLocalEndpoints() {
39+
if (endpoints.isEmpty()) {
40+
return true;
41+
}
42+
for (ApplicationProperties.ProcessExecutor.UnoServerEndpoint ep : endpoints) {
43+
if (isLocalEndpoint(ep)) {
44+
return true;
45+
}
46+
}
47+
return false;
48+
}
49+
50+
private static boolean isLocalEndpoint(
51+
ApplicationProperties.ProcessExecutor.UnoServerEndpoint ep) {
52+
if (ep == null) {
53+
return true;
54+
}
55+
String loc = ep.getHostLocation();
56+
if ("remote".equalsIgnoreCase(loc)) {
57+
return false;
58+
}
59+
if ("local".equalsIgnoreCase(loc)) {
60+
return true;
61+
}
62+
String host = ep.getHost();
63+
if (host == null || host.isBlank()) {
64+
return true;
65+
}
66+
host = host.trim().toLowerCase(Locale.ROOT);
67+
return "127.0.0.1".equals(host) || "localhost".equals(host) || "::1".equals(host);
68+
}
69+
3870
public UnoServerLease acquireEndpoint() throws InterruptedException {
3971
if (endpoints.isEmpty()) {
4072
return new UnoServerLease(defaultEndpoint(), null, this);

app/common/src/test/java/stirling/software/common/util/UnoServerPoolTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,31 @@ void testHostLocationAndProtocol() throws InterruptedException {
259259
}
260260
}
261261

262+
@Test
263+
void testHasLocalEndpoints() {
264+
// Empty pool defaults to local
265+
assertTrue(new UnoServerPool(Collections.emptyList()).hasLocalEndpoints());
266+
267+
// Pool with 127.0.0.1 (default auto) is local
268+
assertTrue(new UnoServerPool(createEndpoints(1)).hasLocalEndpoints());
269+
270+
// Pool with explicit remote location is not local
271+
ApplicationProperties.ProcessExecutor.UnoServerEndpoint remoteEp =
272+
new ApplicationProperties.ProcessExecutor.UnoServerEndpoint();
273+
remoteEp.setHost("unoserver1");
274+
remoteEp.setHostLocation("remote");
275+
UnoServerPool remotePool = new UnoServerPool(Collections.singletonList(remoteEp));
276+
assertFalse(remotePool.hasLocalEndpoints());
277+
278+
// Pool with explicit local location is local
279+
ApplicationProperties.ProcessExecutor.UnoServerEndpoint localEp =
280+
new ApplicationProperties.ProcessExecutor.UnoServerEndpoint();
281+
localEp.setHost("unoserver1");
282+
localEp.setHostLocation("local");
283+
UnoServerPool localPool = new UnoServerPool(Collections.singletonList(localEp));
284+
assertTrue(localPool.hasLocalEndpoints());
285+
}
286+
262287
private List<ApplicationProperties.ProcessExecutor.UnoServerEndpoint> createEndpoints(
263288
int count) {
264289
List<ApplicationProperties.ProcessExecutor.UnoServerEndpoint> endpoints = new ArrayList<>();

docker/unoserver/entrypoint.sh

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,20 @@ start_unoserver() {
5858
log "Starting unoserver on ${INTERFACE}:${PORT} (uno-port ${UNO_PORT}, timeout ${CONVERSION_TIMEOUT}s, profile ${PROFILE_DIR})"
5959
# Pass --user-installation as a plain path; unoserver 3.6 wraps it itself
6060
# and crashes if pre-wrapped as a file:// URI.
61+
local demand_file="/tmp/uno-last-used"
6162
unoserver \
6263
--interface "$INTERFACE" \
6364
--port "$PORT" \
6465
--uno-port "$UNO_PORT" \
6566
--user-installation "${PROFILE_DIR}" \
6667
--conversion-timeout "$CONVERSION_TIMEOUT" \
67-
2> >(grep --line-buffered -v "POST /RPC2" >&2) \
68+
2> >(while read -r line; do
69+
if [[ "$line" == *"POST /RPC2"* ]]; then
70+
date +%s > "$demand_file" 2>/dev/null || true
71+
else
72+
printf '%s\n' "$line" >&2
73+
fi
74+
done) \
6875
&
6976
UNOSERVER_PID=$!
7077
}
@@ -90,7 +97,7 @@ recycle_supervisor() {
9097
fi
9198

9299
if [ "$IDLE_TIMEOUT" -gt 0 ]; then
93-
log "Idle shutdown enabled: stop after ${IDLE_TIMEOUT}s of inactivity"
100+
log "Idle shutdown enabled: stop after ${IDLE_TIMEOUT}s of inactivity (Note: remote clients cannot wake an idle-stopped standalone instance over TCP)"
94101
fi
95102

96103
# Track last activity via demand file (Java writes to this)

0 commit comments

Comments
 (0)