@@ -33,19 +33,10 @@ ERROR clientId=2: Process exited with code 1 before connecting to socket
3333ERROR Shared server process exited with code 1 before becoming ready
3434----
3535
36- For most failures (bad JVM args, missing classpath entry, OOM at boot), the
37- parent logger additionally prints the tail of the child's stderr immediately
38- after the exit-code line:
39-
40- [source]
41- ----
42- ERROR clientId=2: child stderr tail:
43- Error: Could not find or load main class org.apache.tika.pipes.core.server.PipesServer
44- ----
45-
46- For native crashes (segfault in a JNI parser, JVM bug), the JVM writes an
47- `hs_err_pid<N>.log` file to the child's working directory. The parent logger
48- will read and print that file too:
36+ For *native* JVM crashes (e.g. a segfault in a JNI parser), the JVM writes an
37+ `hs_err_pid<N>.log` file. We direct that via `-XX:ErrorFile=` into the
38+ manager's per-server temp directory, then read it into the parent's SLF4J
39+ logger before cleanup:
4940
5041[source]
5142----
@@ -57,51 +48,84 @@ ERROR clientId=2: JVM crash log hs_err_pid12345.log:
5748...
5849----
5950
60- In short: read the * parent* application's log first. The diagnostics from the
61- dead child are inlined there, so you don't have to find anything on disk .
51+ So for native crashes, read the parent application's log first -- the hs_err
52+ contents are inlined there.
6253
63- == Keeping child log files for post-mortem analysis
54+ == Child JVM stdout/stderr
6455
65- By default, each forked server's stdout, stderr, and any JVM crash logs are
66- written into a per-server temp directory. The temp directory is cleaned up
67- when the manager shuts the server down. If you need to keep those files
68- around -- for example, to diff stderr across multiple failed restart attempts,
69- or to ship crash logs to a support contact -- set the
70- `tika.pipes.server.logDir` system property on the *parent* JVM:
56+ By default the child `PipesServer` JVM inherits its stdout and stderr from
57+ the parent. This is the 12-factor / container-friendly default: when Tika
58+ runs in Docker or Kubernetes, the pipes-server's log records flow through
59+ to the container's stdio stream where the runtime (Docker, containerd) and
60+ any log aggregator (fluentd, fluent-bit, Promtail, the K8s log API, etc.)
61+ pick them up automatically. The default `pipes-fork-server-default-log4j2.xml`
62+ writes to `SYSTEM_ERR`, so inheritance is what makes those records visible
63+ to your observability stack.
7164
72- [source,bash]
73- ----
74- java -Dtika.pipes.server.logDir=/var/log/tika-pipes-crashes \
75- -jar your-app.jar ...
76- ----
65+ If you don't want the pipes-server's output interleaved with your own --
66+ e.g. an embedded use case where the parent is producing its own structured
67+ stdout, or a test environment where you want a quieter console -- set the
68+ system property `tika.pipes.server.stdio=discard` on the parent JVM:
7769
78- When set, the manager copies the child's `server-stdout.log`,
79- `server-stderr.log`, and any `hs_err_pid*.log` files to that directory on
80- every abnormal exit, with a timestamp prefix:
81-
82- [source]
70+ [source,bash]
8371----
84- /var/log/tika-pipes-crashes/
85- 1748307123456-server-stderr.log
86- 1748307123456-hs_err_pid12345.log
87- 1748307145001-server-stderr.log # later restart attempt
72+ java -Dtika.pipes.server.stdio=discard -jar your-app.jar ...
8873----
8974
90- The property is off by default. Leave it off in steady-state production; turn
91- it on when you are actively debugging a recurring fork failure.
92-
93- == What does *not* go to those files
94-
95- Steady-state log output from the parser (every parse, every emitter, every
96- embedded-document warning) does **not** go to `server-stderr.log`. It goes
97- through SLF4J inside the child JVM and lands in whatever your `log4j2.xml` or
98- `logback.xml` directs it to. The child's stderr is only useful for things the
99- JVM writes before logging is wired up, or that bypass logging entirely:
100-
101- * JVM startup errors (bad classpath, unrecognized flag, "could not find main
102- class").
103- * Uncaught throwables on the main thread that never reached an SLF4J logger.
104- * Output from `System.err.println` calls (if any).
105-
106- For native crash investigation, the JVM-generated `hs_err_pid<N>.log` is the
107- primary artifact, and it is collected automatically as described above.
75+ With this set, the child's stdout and stderr are routed to the null sink
76+ and the pipes server's log records are silently dropped at the OS level.
77+ (Records written via SLF4J inside the child can still be captured by
78+ configuring `log4j2.xml` / `logback.xml` to write to your own file or
79+ network appender, independent of the stdio setting.)
80+
81+ === Safety of the inherit default on Windows
82+
83+ Earlier versions of Tika hit a surefire hang on Windows when inheriting
84+ child stdio: a forked child held a duplicate of the parent JVM's stderr
85+ handle, and any reader upstream of the parent (a maven-surefire controller,
86+ typically) never saw EOF after the parent died -- the child kept the pipe
87+ open. That class of hang is now mitigated structurally: every child
88+ `PipesServer` watches its parent's process handle via
89+ `ProcessHandle.onExit()` (see <<parent-death-detection>>) and self-
90+ terminates within milliseconds of parent exit. The inherited handle is
91+ released essentially synchronously with the parent's death, and upstream
92+ readers see EOF promptly.
93+
94+ [#parent-death-detection]
95+ == Parent-death detection
96+
97+ The child `PipesServer` JVMs watch their parent's PID via
98+ `ProcessHandle.onExit()` and self-terminate within milliseconds if the
99+ parent dies. The parent passes its own PID via the
100+ `TIKA_PIPES_PARENT_PID` environment variable when spawning the child.
101+
102+ This matters because the parent (e.g. tika-server) can be killed in ways
103+ that skip its JVM shutdown hooks -- for instance,
104+ `Process.destroy()` on Windows is equivalent to `TerminateProcess`, which
105+ bypasses all hooks. Without parent-death detection, an orphaned PipesServer
106+ would only notice via TCP RST on its next socket read, and would not
107+ notice at all while busy in a parse, leaving it (and any external
108+ subprocess it had spawned, such as a tesseract OCR worker) running
109+ indefinitely.
110+
111+ When the watcher fires, the child exits via `System.exit`, which runs
112+ `AbstractExternalProcessParser`'s shutdown hook and cleans up any
113+ in-flight external subprocesses.
114+
115+ == Configuration knobs reference
116+
117+ [cols="2,3"]
118+ |===
119+ |System property / env var |Effect
120+
121+ |`tika.pipes.server.stdio` (system property)
122+ |`discard` suppresses child stdout/stderr at the OS level. Anything else
123+ (or unset) inherits the child's stdio from the parent JVM. Default: inherit.
124+
125+ |`TIKA_PIPES_PARENT_PID` (env var)
126+ |Set automatically by the parent manager when spawning a `PipesServer`
127+ child. The child uses it to watch its parent and self-terminate if the
128+ parent dies. Not normally set by users; if you launch `PipesServer`
129+ standalone (outside the normal manager flow) and leave it unset, the
130+ parent-watch is simply skipped.
131+ |===
0 commit comments