FEATURE AVAILABILITY STARTING FROM RELEASE 2.8.2
Introduction
JS7 - GraalVM Python Jobs are based on Oracle® GraalVM.
This article explains how a GraalVM Python Job can execute an external application or script in a subprocess and forward its stdout and stderr output to the JS7 logger in real time.
This enables JS7 workflows to integrate existing scripts, tools or services while maintaining full control over log streaming and cancellation behavior.
Example
For further details on the JS7 Job API, see JS7 - Job API
The following example demonstrates how to process stdout and stderr output from a subprocess in parallel using the Python threading module.
Additionally, the onProcessOrderCanceled() hook is used to terminate the subprocess if the order is canceled.
import subprocess
import threading
def stream_output(stream, logger, prefix):
for line in iter(stream.readline, ""):
logger(f"{prefix}{line.rstrip()}")
stream.close()
class JS7Job(js7.Job):
def processOrder(self, js7Step):
logger = js7Step.getLogger()
# e. g. '/usr/bin/python3', '/bin/bash', ...
runtime_path = js7Step.getAllArgumentsAsNameValueMap().get("runtime_path")
# Path to the application or script
app_path = js7Step.getAllArgumentsAsNameValueMap().get("app_path")
# Arguments that will be passed to the application or script
args = ["js7Step_argument_1", "js7Step_argument_2"]
logger.info(f"[subprocess]start...")
cmd = [runtime_path]
# Unbuffered mode only for Python
if runtime_path.endswith("python") or runtime_path.endswith("python3"):
cmd.append("-u")
cmd.append(app_path)
cmd += args
# Starts the application or script as a subprocess
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
js7Step.setCancelableResource(process)
# Defines the thread for live stdout
stdout_thread = threading.Thread(
target=stream_output,
args=(process.stdout, logger.info, "[LIVE STDOUT] "),
daemon=True
)
# Defines the thread for live stderr
stderr_thread = threading.Thread(
target=stream_output,
args=(process.stderr, logger.error, "[LIVE STDERR] "),
daemon=True
)
# Starts the threads
stdout_thread.start()
stderr_thread.start()
returncode = process.wait()
# Waits for the threads to finish
stdout_thread.join()
stderr_thread.join()
logger.info(f"[subprocess]returncode={returncode}")
js7Step.getOutcome().setReturnCode(returncode)
js7Step.getOutcome().putVariable("outcome_var_name", "outcome_var_value")
# Uses the onProcessOrderCanceled hook after the order has been canceled
def onProcessOrderCanceled(self, js7Step):
process = js7Step.getCancelableResource()
js7Step.getLogger().info(f"onProcessOrderCanceled={process}")
# Kills the process if it exists
if process is not None:
process.kill()
Note: Subprocesses run independently of the GraalVM Python environment. Long-running scripts or tools can stream output to the JS7 logger continuously without causing the job to hang or become unresponsive.
Resources
Downloads
- Download example workflow (upload .json): pdCancellableSubprocessExample.workflow.json