Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 0.31.0 (2026-08-10)

### Features
* Rust session runtime adapter: sessions can run on the OpenJD v1 Rust runtime as an alternative to the Python runtime. Select it by setting `session_runtime` in worker.toml to `python`, `rust`, or `service-selected`. (#1002)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This release changes required dependency ranges in an install-breaking way, and none of it is listed. Between 290e493 (the 0.30.2 release commit) and this head, pyproject.toml changed:

-    "openjd-sessions == 0.10.10",
-    "openjd-model >= 0.8.1, < 0.11",
+    "openjd-sessions == 0.10.13",
+    "openjd-model >= 0.11.1, < 0.12",

(plus deadline-job-attachments == 0.1.20.1.3)

The openjd-model change is the significant one: the new floor (>= 0.11.1) sits above the previous ceiling (< 0.11), so the ranges are disjoint. Any environment that currently pins or resolves openjd-model in the 0.8–0.10 band — a co-installed submitter, plugin, or vendored constraints file — cannot satisfy both and will fail to resolve on upgrade to 0.31.0. openjd-sessions is an exact pin, so it too must move in lockstep.

Given the repo's own note that the openjd-sessions pin exists "due to Host Config Script runner usage of private OpenJD Sessions API", these bounds are load-bearing and worth surfacing to operators rather than leaving them to discover at pip install time. Suggest adding under a Dependencies heading (or BREAKING CHANGES if the disjoint range is considered breaking):

Dependencies

  • openjd-model requirement moved to >= 0.11.1, < 0.12 (previously >= 0.8.1, < 0.11) and the openjd-sessions pin to 0.10.13, required by the Rust session runtime and step_name support. Environments pinning openjd-model below 0.11.1 will need to upgrade. deadline-job-attachments pinned to 0.1.3.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate point from my dependency comment on this line: this bullet presents the Rust runtime as a drop-in peer of the Python runtime ("as an alternative to"), but the adapter carries documented functional gaps that an operator flipping session_runtime = "rust" in production would want to know about up front.

From sessions/runtime/rust.py:

  • Attachment sync runs through a stopgap path. _run_task_without_session_env (line 336) does not delegate to the runtime at all — it materializes embedded files itself, resolves {{ Task.File.* }} by regex, and shells out via run_subprocess. The comment at line 353-357 labels it "STOPGAP" and notes that delegating properly "would also restore the library's cross-platform file permission handling", i.e. permission handling on this path is currently hand-rolled and POSIX-conditional (line 375-381).
  • Unsupported OpenJD extensions are silently skipped, not rejected. Line 224-232 logs a warning and drops any extension the Rust crate does not know (WRAP_ACTIONS is named as an example). Since Session.__init__ requests all extensions unconditionally (sessions/session.py:226), the drop is routine, and the failure surfaces later at template-decode time rather than at selection.
  • Only OpenJD 2023-09 is supported (_SPEC_REVISIONS, line 62); any other revision raises ValueError at construction.

Suggest tempering the wording so rust reads as opt-in/preview rather than equivalent, e.g. appending: "The Rust runtime supports OpenJD revision 2023-09, silently ignores model extensions the Rust crate does not implement, and uses an interim implementation for the job-attachment sync path; the Python runtime remains the default."

* With `service-selected`, the session runtime (Python or Rust) is chosen from a `runtimeHint` provided by the service, defaulting to Python when no hint is given. (#1009, #1016)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This describes the happy path but omits the failure mode an operator who opts into service-selected is taking on: an unrecognized runtimeHint does not fall back to Python — it fails the session.

select_runtime() treats _RUNTIME_HINT_MAP as a strict allowlist and raises ValueError on any value outside {"pythonexpr", "rust"} (sessions/runtime/_select.py:52-56). The scheduler catches that and calls _fail_all_actions() (scheduler.py:1173-1178), so the first action reports FAILED and the rest NEVER_ATTEMPTED. Only an absent hint defaults to Python.

That matters because the failure is triggered by a service-side value the operator does not control, and the commit's own comment names version skew as the expected cause. An operator reading "defaulting to Python when no hint is given" would reasonably assume service-selected is fail-safe against a service that starts sending a new RuntimeMode value this agent version predates; it is not.

Worth stating explicitly, e.g.:

* Runtime selection and failure telemetry events added. To opt out, set `opt_out = true` under `[telemetry]` in worker.toml, pass `--telemetry-opt-out` to the installer, or set the `DEADLINE_CLOUD_TELEMETRY_OPT_OUT=true` environment variable. (#1021)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opt-out instructions here are accurate, but the entry says nothing about what the new events transmit — and this release starts sending identifiers that prior telemetry did not.

Both new events carry farm_id, queue_id, session_id, and region in event_details (aws/deadline/__init__.py:938-947 for runtime_selection, and the same set in record_runtime_failure_telemetry_event). runtime_selection fires once per session (scheduler.py:1214), so this is a per-session stream of farm/queue/session identifiers, not an aggregate counter.

For a public release note on an opt-out telemetry system, the collected fields are the part operators need in order to make the opt-out decision the entry is inviting them to make. Note the implementation is visibly careful here — exception text is deliberately replaced with constants specifically to keep filesystem paths and other free text out of telemetry (see the comments at scheduler.py:1264-1270 and 1188-1191) — which is worth the credit, and reinforces that the fields that are sent were a deliberate choice worth documenting.

Suggested addition:

  • Runtime selection and failure telemetry events added. Each event records the selected runtime, the reason for the selection, and the associated farm, queue, session, and region identifiers; one selection event is emitted per session. To opt out, set opt_out = true under [telemetry] in worker.toml, pass --telemetry-opt-out to the installer, or set DEADLINE_CLOUD_TELEMETRY_OPT_OUT=true. (feat: emit runtime selection and failure telemetry events #1021)


### Bug Fixes
* Wrap-environment jobs failed on both the Python and Rust runtimes because `step_name` wasn't forwarded, leaving RFC 0008's `WrappedStep.Name` unresolved; both runtime paths now forward it. (#1039, #1040)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entry describes a user-facing failure ("Wrap-environment jobs failed") that, per the implementing commit itself, no user could actually hit. 66ecb5c's message states: "No wrap-action job can reach either runtime today, since the service does not yet accept extension requests at job submission." Filing it under Bug Fixes with past-tense "jobs failed" will send operators hunting for a regression they never experienced, and may prompt unnecessary upgrades.

Two further inaccuracies in "both runtime paths now forward it":

  • The Python adapter forwards step_name from run_task (sessions/runtime/python.py:90) but deliberately not from _run_task_without_session_env (line 100-109, with an explanatory comment). The attachment-sync path is therefore still unforwarded on the Python runtime.
  • The Rust adapter falls back to the literal "Placeholder" when step_name is None (sessions/runtime/rust.py:324), so WrappedStep.Name still resolves to a bogus value for any caller that omits it.

Consider moving this under Features (or a "Preparatory work" note) and stating the forward-looking framing, e.g.:

* Rust runtime panics no longer silently kill the session thread; they are now reported as a failed session with proper cleanup and telemetry. (#1026)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"no longer silently kill the session thread" is broader than what 6080bc9 actually protects. convert_runtime_crashes is applied only to the adapter's instance methods (sessions/runtime/rust.py:258, 287, 301, 336, 406, 414, 423, 428, 433). Two PyO3 boundaries are left undecorated:

  • RustSessionRuntime.__init__ (line 206). The OpenJDRustSession(...) construction at line 246 is not wrapped. A panic there escapes as a BaseException, and the only guard at the call site — scheduler.py:1249 — catches (ValueError, NotImplementedError, OSError). So a construction-time panic still propagates out of the scheduler's loop uncaught, which is a strictly worse outcome than the session-thread death this entry claims to have fixed.
  • _rust_action_callback (line 243). This runs on a Rust-owned thread. A panic raised there does not traverse any decorated method, so it is neither converted nor attributed.

Either narrow the wording to the boundary that is actually covered, e.g.:

…or, preferably, close the __init__ gap so the original claim holds.

* Transient network errors (connection closed, connect/read timeout, endpoint connection) are now retried with exponential backoff instead of terminating the agent. (#1013)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entry overstates the scope of #1013. The retry handling added in af02f3f is wired into exactly one call site — update_worker_schedule() in src/deadline_worker_agent/aws/deadline/__init__.py:858. Every other Deadline API call in that module (assume_fleet_role_for_worker, create_worker, batch_get_job_entity, update_worker, …) still funnels transport errors into the generic except ExceptionDeadlineRequestUnrecoverableError path. As written, an operator would reasonably read this as agent-wide resilience to network blips, which is not what shipped.

Also, the parenthetical list of error types is narrower than the code: the implementation catches the botocore base classes ConnectionError and HTTPClientError (_TRANSIENT_NETWORK_EXCEPTIONS, line 57), which additionally covers SSLError, ProxyConnectionError, and ResponseStreamingError.

Suggested rewording:

* Credentials expiring mid-call during hibernate/sleep no longer cause an unrecoverable exit; the agent now detects the time jump and retries with bootstrap credentials. (#1014)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two accuracy problems with this entry.

1. Scope is agent-wide but the fix is not. 528cd41 touches only WorkerBoto3Session.refresh_credentials() (src/deadline_worker_agent/aws_credentials/worker_boto3_session.py:101-122), i.e. fleet-role credentials. QueueBoto3Session.refresh_credentials() (queue_boto3_session.py:265+) has no equivalent re-check — a hibernate/sleep that expires queue-role credentials mid-AssumeQueueRoleForWorker still propagates DeadlineRequestUnrecoverableError unchanged. As worded, an operator would read this as covering credential refresh generally.

2. "detects the time jump" is not what the code does. There is no clock-delta or monotonic-vs-wall comparison anywhere in the fix. It simply re-calls credentials_object.are_expired() in the except handler and retries if the credentials are now expired and bootstrap was not already in use. The implementation's own comment says so explicitly (line 107): "are_expired() is a heuristic, not proof of the failure's cause… A genuine AccessDeniedException (e.g. policy change, role deletion) that happens to coincide with expiry will also be retried here." The changelog asserts a detection capability the code does not have.

Suggested rewording:

## 0.30.2 (2026-07-14)

### Features
Expand Down
Loading