- All Python stdout goes to Appose protocol -- logging MUST use
sys.stderr, never print() or sys.stdout
- NumPy pre-warm required on Windows -- prepend
import numpy in init() before any NumPy-using scripts. Since init() replaces on each call, combine into a single call: init("import numpy\n" + mainScript)
- init() is lazy -- does NOT start Python or execute scripts. You MUST run a blocking
task().waitFor() after init() to verify the environment. Without this, initialize() returns "success" in milliseconds without ever starting Python.
- init() replaces, does not append -- calling
init() twice silently discards the first script. Our Windows NumPy workaround was being silently lost before this was discovered.
- NDArray must be closed -- shared memory leaks if
close() is not called
- Multi-threaded worker requires serialization -- Appose runs each
task() in its own thread within a single Python subprocess. Without inference_lock, concurrent overlay tiles race on model loading, CUDA memory, and forward passes. All GPU-accessing scripts must use with inference_lock:. Concurrent tasks in the same subprocess can cause Appose IPC message ordering races ("thread death").
- ServiceLoader classloader isolation -- NDArray allocation triggers
ServiceLoader.load(ShmFactory.class) on every call with no caching. Must set TCCL to extension classloader.
- init_services.py must propagate ImportErrors -- if critical packages (torch, smp) are missing, the init script must fail loudly. Only catch runtime errors (no GPU, etc.) so the Java side knows the environment is broken.
task.inputs is private in 0.10.0 -- Python scripts CANNOT use task.inputs["key"]. Inputs are injected directly into the script's execution scope as variables. Use model_path directly instead of task.inputs["model_path"]. For optional inputs, use try: reflection_padding / except NameError: reflection_padding = 0. task.outputs remains public and writable.
- Init script exports STRIP underscore-prefixed names -- Appose's python_worker explicitly filters out names starting with
_ when exporting init script globals to task scripts: if not key.startswith('_'). This means _my_var defined in init_services.py is INVISIBLE to task scripts, while my_var is visible. NEVER use underscore-prefixed names for globals that task scripts need.
For each of these, consider whether and how to modify Appose to ease future pain.
sys.stderr, neverprint()orsys.stdoutimport numpyininit()before any NumPy-using scripts. Sinceinit()replaces on each call, combine into a single call:init("import numpy\n" + mainScript)task().waitFor()afterinit()to verify the environment. Without this,initialize()returns "success" in milliseconds without ever starting Python.init()twice silently discards the first script. Our Windows NumPy workaround was being silently lost before this was discovered.close()is not calledtask()in its own thread within a single Python subprocess. Withoutinference_lock, concurrent overlay tiles race on model loading, CUDA memory, and forward passes. All GPU-accessing scripts must usewith inference_lock:. Concurrent tasks in the same subprocess can cause Appose IPC message ordering races ("thread death").ServiceLoader.load(ShmFactory.class)on every call with no caching. Must set TCCL to extension classloader.task.inputsis private in 0.10.0 -- Python scripts CANNOT usetask.inputs["key"]. Inputs are injected directly into the script's execution scope as variables. Usemodel_pathdirectly instead oftask.inputs["model_path"]. For optional inputs, usetry: reflection_padding / except NameError: reflection_padding = 0.task.outputsremains public and writable._when exporting init script globals to task scripts:if not key.startswith('_'). This means_my_vardefined ininit_services.pyis INVISIBLE to task scripts, whilemy_varis visible. NEVER use underscore-prefixed names for globals that task scripts need.For each of these, consider whether and how to modify Appose to ease future pain.