apache/seatunnel · error · IOException
Timed out waiting for Python source stdout to close after…
Error message
Timed out waiting for Python source stdout to close after the process exited; ensure child processes do not inherit stdout
What it means
After the Python process exits, the source waits for the stdout pump thread to drain and close. If stdout is still open past the deadline, a child process inherited the stdout pipe, keeping it open forever; the reader throws this timeout error instead of hanging.
Solutions
- Fix the Python script so child processes redirect/close stdout/stderr (subprocess.Popen(..., stdout=DEVNULL) or pass close_fds=True)
- Ensure spawned workers use their own pipes/queues rather than the parent's stdout protocol channel
- As a workaround, suppress or detach children (daemonize with os.setsid) so stdout EOF arrives when the main process exits
Example fix
# before: children inherit stdout, blocking EOF cmd = ["worker"] subprocess.Popen(cmd) # after subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, close_fds=True)
Defensive patterns
Strategy: try-catch
Validate before calling
# audit your Python source for child processes that inherit stdout import subprocess, sys subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, close_fds=True)
Try / catch
try {
reader.awaitTermination();
} catch (Exception e) {
if (e.getMessage().contains("stdout to close")) {
// fix the Python script's child process stdio handling and rerun
}
throw e;
} Prevention
- Never let child processes inherit the stdout used by the SeaTunnel protocol
- Redirect children's stdout/stderr to DEVNULL, files, or pipes
- Avoid forked daemons in Python source scripts; daemonize with new session fds
- Test scripts under multiprocessing to confirm stdout EOF arrives at exit
When it happens
Trigger: Python source process spawns child processes (workers, subprocess.Popen without stdout redirect) that inherit the parent's stdout; stdout pump thread never sees EOF before stdoutCloseDeadlineNanos elapses.
Common situations: Python scripts using multiprocessing/Dask/Spark-like children; invoking shell commands from the Python source that inherit descriptors; long-lived daemons forked by the script.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Failed to consume python source stderr
- Failed to consume python source stdout
- Failed to deserialize python source stdout line
- Timed out after seconds while writing python.script.config…
- Ambiguous timeout on Couchbase write
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/7e118d3dff244176.
Report an issue: GitHub.
Appendix: source
Thrown at seatunnel-connectors-v2/connector-python/src/main/java/org/apache/seatunnel/connectors/seatunnel/python/source/PythonSourceReader.java:563
* Gives stdout one short drain interval per poll after the direct process exits.
*
* @return true when stdout reached EOF with no buffered rows, or false when polling must
* continue or engine cancellation won the race
*/
private boolean awaitStdoutPumpAfterProcessExit() throws IOException {
if (closeRequested) {
return false;
}
if (stdoutCloseDeadlineNanos == 0L) {
stdoutCloseDeadlineNanos =
System.nanoTime() + TimeUnit.SECONDS.toNanos(PROCESS_DESTROY_TIMEOUT_SECONDS);
}
if (!isAlive(stdoutPumpThread)) {
return !closeRequested && stdoutLines.isEmpty();
}
if (!stdoutLines.isEmpty()) {
if (System.nanoTime() >= stdoutCloseDeadlineNanos) {
throw inheritedStdoutTimeout();
}
return false;
}
long remainingNanos = stdoutCloseDeadlineNanos - System.nanoTime();
if (remainingNanos <= 0) {
throw inheritedStdoutTimeout();
}
long waitMillis =
Math.min(
PROCESS_EXIT_CHECK_TIMEOUT_MILLIS,
Math.max(1L, TimeUnit.NANOSECONDS.toMillis(remainingNanos)));
try {
stdoutPumpThread.join(waitMillis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException(
"Interrupted while draining Python source stdout after process exit", e);View on GitHub (pinned to cf67b549a7)