3.9 KiB
Task startup and notification loading
task performs shell configuration, status reporting and dependency-path discovery
before invoking the selected runtime. These costs are separate from cron callback
latency and time spent waiting for an execution slot.
Dependency-path cache
Node scheduling usually starts in /ql, while Alpine crond starts in /root.
The pnpm discovery key includes the working directory, npm/pnpm configuration,
relevant environment and executable metadata. Each key now has its own cached
record, so alternating scheduler environments do not evict each other.
Records expire after 60 seconds. Refreshes share a persistent per-user lock;
records older than an hour are removed on refresh. Warm lookups do not scan the
cache directory. The lock is never removed while another process might use it.
The opt-out QL_NODE_PATH_CACHE=0, failed-discovery fallback, and package resolution
order remain available. This caches the dependency directory, not its contents.
Language startup paths
| Invocation | Environment and before hooks | Automatic notification loading |
|---|---|---|
task script.py / .pyc |
Python preload imports generated environment, runs the shell/command before hooks, imports task_before.py, then applies account selection |
QLAPI.notify(...) imports __ql_notify__ on first call |
task script.js / .mjs / .ts |
Node preload imports generated environment, runs shell/command before hooks, requires task_before.js, then applies account selection |
QLAPI.notify(...) requires __ql_notify__.js on first call |
task script.sh |
Shell sources the generated environment and runs shell before hooks | No automatic Python/Node notification module |
Explicit interpreter or other command, e.g. task python3 script.py |
Retains generated environment and shell before-hook exports; it does not automatically install the Python/Node preload | No automatic notification module unless configured by the caller |
TypeScript uses ts-node-transpile-only; MJS uses the same Node preload plus the
ESM loader. There are no separate built-in Ruby, Go, Java or PHP preload modules.
All these task paths still pass through shared dependency-path setup: non-JS
scripts and their hooks may themselves call Node or use the QLAPI bridge.
The before hooks are deliberately still synchronous: they can export environment variables or perform work required by the user's script. Python's preload still starts a child shell/Python to capture that environment; Node's preload starts a child shell/Node. Node also still loads its gRPC client eagerly. These are remaining startup costs, not work performed by the cron library, and are not removed by this change.
Shell scripts run in a child shell that inherits the wrapper's environment and
before-hook functions. This lets the wrapper run its after hooks and report the
exit status even when a script calls exit, replaces itself with exec, or fails
under set -e. The script's own EXIT trap still runs in the child. Changes made
inside the script (variables, working directory, functions and shell options)
do not propagate back to the wrapper or its after hooks.
Lazy notifications
Scripts keep calling QLAPI.notify with the same arguments. When no notification
is sent, the notification provider and its channel dependencies are never imported
by the preload. First use loads the provider; subsequent uses reuse the standard
Python/Node module cache. JS forwards the receiver and return value, including
promises, and Python forwards positional and keyword arguments. Import/send errors
surface at the notification call rather than preventing unrelated scripts from
obtaining QLAPI during startup. A failed import can be retried after its cause is
resolved.
Provider initialization, including provider configuration reads, now happens at
first use. A script's explicit import __ql_notify__, require(...), or independent
notification helper still loads that module immediately.