Skip to content

[SQL][SPARK-57318] Refactor WorkerSession into a state-machine interface - #56397

Closed
haiyangsun-db wants to merge 6 commits into
apache:masterfrom
haiyangsun-db:pr2-udf-core-session-refactor
Closed

[SQL][SPARK-57318] Refactor WorkerSession into a state-machine interface#56397
haiyangsun-db wants to merge 6 commits into
apache:masterfrom
haiyangsun-db:pr2-udf-core-session-refactor

Conversation

@haiyangsun-db

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

This PR refactors the experimental UDF worker session interface in the udf-worker-core module into an explicit state machine with well-defined terminal outcomes, so that a concrete transport backend can plug in cleanly. (The first such backend -- a gRPC-over-UDS dispatcher -- lands in a follow-up PR.)

Core interface changes:

  • WorkerSession becomes a single AtomicReference-driven state machine (Created -> Initializing -> Initialized -> Streaming -> Finishing, with Cancelling/Finished/Cancelled/Failed/TransportFailed terminals). init now returns an InitResponse, process takes a finish thunk, and close returns a Termination instead of throwing; the previous separate cancel() is folded into close(). Subclasses implement doInit / doProcess / doClose hooks and drive transitions via protected helpers.
  • Termination (new) is a sealed trait modeling the four terminal outcomes: Finished(FinishResponse), Cancelled(CancelResponse), Failed(ExecutionError), and TransportFailed(Throwable).
  • WorkerHandle (new) is a small trait that decouples a session from the concrete worker-provisioning model. DirectWorkerProcess implements it and gains a cleanup-hook registry, so a dispatcher can register transport-specific cleanup (e.g. deleting a socket file) without the core hard-coding it.
  • DirectWorkerDispatcher is generalized from a Unix-socket-specific class into abstract transport hooks (newEndpointAddress / waitForReady / cleanupEndpointAddress / closeTransport / validateTransportSupport / newConnection / newSession / initialize). The concrete Unix-socket dispatcher and session (DirectUnixSocketWorkerDispatcher, DirectWorkerSession) are removed -- the first concrete backend is provided by the follow-up gRPC change.
  • WorkerConnection becomes a trait.

Caller/test changes:

  • ExternalUDFExec (sql/core) is updated to the new close() / Termination finalizer: a single task-completion listener now handles both the success path (a FinishResponse has already arrived) and the failure/early-stop path (send Cancel, await CancelResponse), replacing the previous separate cancel-on-failure listener.
  • The in-core test scaffolding (TestDirectWorkerHelpers, DirectWorkerDispatcherSuite) is adapted to the new API as a small UDS-backed test dispatcher, so udf-worker-core and sql/core stay green. This scaffolding moves to the udf-worker-grpc module alongside the concrete gRPC dispatcher in the follow-up PR.

Class hierarchy after this change:

WorkerSession (abstract state machine; close(): Termination)
WorkerHandle  (trait)  <- DirectWorkerProcess
DirectWorkerDispatcher (abstract transport hooks)
WorkerConnection (trait)
Termination = Finished | Cancelled | Failed | TransportFailed

Why are the changes needed?

The previous WorkerSession tracked its lifecycle with a pair of AtomicBooleans and signaled completion/cancellation by throwing, which does not cleanly express the several terminal outcomes a worker session can reach (clean finish, cancellation, user/worker error, transport failure) nor make worker reuse decisions explicit. It was also tied to a single Unix-socket transport. Reshaping it into an explicit state machine with a Termination result and abstract transport hooks gives a precise, testable lifecycle contract and lets a concrete transport (gRPC over UDS, in the follow-up) implement a small set of hooks rather than re-deriving lifecycle and termination handling. WorkerHandle and the cleanup-hook registry remove the core's dependence on a specific provisioning model.

Does this PR introduce any user-facing change?

No. The UDF worker framework is experimental and currently unused by any released code path; this PR only reshapes its internal interfaces.

How was this patch tested?

Existing and adapted unit tests, run on top of master:

  • build/sbt udf-worker-core/Test/compile and build/sbt sql/Test/compile succeed.
  • build/sbt "udf-worker-core/testOnly *DirectWorkerDispatcherSuite" -- the dispatcher process-lifecycle suite (spawn / wait-for-ready / cleanup / error / timeout / concurrency) passes (31 tests) against the adapted in-core UDS test dispatcher.
  • PythonUDFWorkerSpecificationSuite (sql/core) is updated to the new close() signature and continues to spawn a real Python worker and verify reachability.

Was this patch authored or co-authored using generative AI tooling?

Yes

@haiyangsun-db haiyangsun-db changed the title [SQL] Refactor WorkerSession into a state-machine interface with Ter… [SQL][SPARK-57318] Refactor WorkerSession into a state-machine interface Jun 9, 2026

@hvanhovell hvanhovell left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

…mination outcomes

    Redesign the experimental UDF worker session interface so a concrete
    transport backend can plug in cleanly:

    - WorkerSession becomes a single AtomicReference-driven state machine
      (Created -> Initializing -> Initialized -> Streaming -> Finishing,
      with Cancelling/Finished/Cancelled/Failed/TransportFailed terminals).
      init now returns InitResponse, process takes a finish thunk, and close
      returns a Termination instead of throwing; cancel() is folded into close.
    - New Termination sealed trait models the four terminal outcomes
      (Finished/Cancelled/Failed/TransportFailed).
    - New WorkerHandle trait decouples a session from the concrete provisioning
      model; DirectWorkerProcess implements it and gains a cleanup-hook registry
      so dispatchers register transport-specific cleanup (e.g. socket deletion).
    - DirectWorkerDispatcher is generalized into abstract transport hooks
      (newEndpointAddress / waitForReady / cleanupEndpointAddress / closeTransport
      / validateTransportSupport / newConnection / newSession / initialize), and
      the concrete Unix-socket dispatcher/session are removed -- the first
      concrete backend lands in the follow-up udf-worker-grpc change.
    - WorkerConnection becomes a trait.

    ExternalUDFExec is updated to the new close()/Termination finalizer (a single
    task-completion listener now handles both success and failure).

    Test scaffolding (TestDirectWorkerHelpers, DirectWorkerDispatcherSuite) is
    adapted to the new API as an in-core UDS test dispatcher so core and sql/core
    stay green; it moves to udf-worker-grpc alongside the concrete gRPC dispatcher
    in the follow-up.
@haiyangsun-db
haiyangsun-db force-pushed the pr2-udf-core-session-refactor branch from f7931df to e4bb07f Compare June 9, 2026 22:48

@sven-weber-db sven-weber-db left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

asf-gitbox-commits pushed a commit that referenced this pull request Jun 10, 2026
### What changes were proposed in this pull request?

This PR refactors the experimental UDF worker **session interface** in the `udf-worker-core` module into an explicit state machine with well-defined terminal outcomes, so that a concrete transport backend can plug in cleanly. (The first such backend -- a gRPC-over-UDS dispatcher -- lands in a follow-up PR.)

Core interface changes:

- **`WorkerSession`** becomes a single `AtomicReference`-driven state machine (`Created -> Initializing -> Initialized -> Streaming -> Finishing`, with `Cancelling`/`Finished`/`Cancelled`/`Failed`/`TransportFailed` terminals). `init` now returns an `InitResponse`, `process` takes a `finish` thunk, and `close` returns a `Termination` instead of throwing; the previous separate `cancel()` is folded into `close()`. Subclasses implement `doInit` / `doProcess` / `doClose` hooks and drive transitions via protected helpers.
- **`Termination`** (new) is a sealed trait modeling the four terminal outcomes: `Finished(FinishResponse)`, `Cancelled(CancelResponse)`, `Failed(ExecutionError)`, and `TransportFailed(Throwable)`.
- **`WorkerHandle`** (new) is a small trait that decouples a session from the concrete worker-provisioning model. `DirectWorkerProcess` implements it and gains a cleanup-hook registry, so a dispatcher can register transport-specific cleanup (e.g. deleting a socket file) without the core hard-coding it.
- **`DirectWorkerDispatcher`** is generalized from a Unix-socket-specific class into abstract transport hooks (`newEndpointAddress` / `waitForReady` / `cleanupEndpointAddress` / `closeTransport` / `validateTransportSupport` / `newConnection` / `newSession` / `initialize`). The concrete Unix-socket dispatcher and session (`DirectUnixSocketWorkerDispatcher`, `DirectWorkerSession`) are removed -- the first concrete backend is provided by the follow-up gRPC change.
- **`WorkerConnection`** becomes a trait.

Caller/test changes:

- `ExternalUDFExec` (sql/core) is updated to the new `close()` / `Termination` finalizer: a single task-completion listener now handles both the success path (a `FinishResponse` has already arrived) and the failure/early-stop path (send `Cancel`, await `CancelResponse`), replacing the previous separate cancel-on-failure listener.
- The in-core test scaffolding (`TestDirectWorkerHelpers`, `DirectWorkerDispatcherSuite`) is adapted to the new API as a small UDS-backed test dispatcher, so `udf-worker-core` and `sql/core` stay green. This scaffolding moves to the `udf-worker-grpc` module alongside the concrete gRPC dispatcher in the follow-up PR.

Class hierarchy after this change:

```
WorkerSession (abstract state machine; close(): Termination)
WorkerHandle  (trait)  <- DirectWorkerProcess
DirectWorkerDispatcher (abstract transport hooks)
WorkerConnection (trait)
Termination = Finished | Cancelled | Failed | TransportFailed
```

### Why are the changes needed?

The previous `WorkerSession` tracked its lifecycle with a pair of `AtomicBoolean`s and signaled completion/cancellation by throwing, which does not cleanly express the several terminal outcomes a worker session can reach (clean finish, cancellation, user/worker error, transport failure) nor make worker reuse decisions explicit. It was also tied to a single Unix-socket transport. Reshaping it into an explicit state machine with a `Termination` result and abstract transport hooks gives a precise, testable lifecycle contract and lets a concrete transport (gRPC over UDS, in the follow-up) implement a small set of hooks rather than re-deriving lifecycle and termination handling. `WorkerHandle` and the cleanup-hook registry remove the core's dependence on a specific provisioning model.

### Does this PR introduce _any_ user-facing change?

No. The UDF worker framework is experimental and currently unused by any released code path; this PR only reshapes its internal interfaces.

### How was this patch tested?

Existing and adapted unit tests, run on top of `master`:

- `build/sbt udf-worker-core/Test/compile` and `build/sbt sql/Test/compile` succeed.
- `build/sbt "udf-worker-core/testOnly *DirectWorkerDispatcherSuite"` -- the dispatcher process-lifecycle suite (spawn / wait-for-ready / cleanup / error / timeout / concurrency) passes (31 tests) against the adapted in-core UDS test dispatcher.
- `PythonUDFWorkerSpecificationSuite` (sql/core) is updated to the new `close()` signature and continues to spawn a real Python worker and verify reachability.

### Was this patch authored or co-authored using generative AI tooling?

Yes

Closes #56397 from haiyangsun-db/pr2-udf-core-session-refactor.

Authored-by: Haiyang Sun <haiyang.sun@databricks.com>
Signed-off-by: Herman van Hövell <herman@databricks.com>
(cherry picked from commit bd04636)
Signed-off-by: Herman van Hövell <herman@databricks.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants