eugenegujing opened a new pull request, #8969:
URL: https://github.com/apache/texera/pull/8969
### What changes were proposed in this PR?
**Root cause.** `ExecutionReconfigurationService.modifyOperatorLogic` calls
the deployed operator's `runtimeReconfiguration` and matches its `Try`: a
`Failure` is answered with `ModifyLogicResponse(isValid = false, message)`,
which the frontend shows as an error notification. That arm was unreachable.
The `LogicalOp` base implementation throws
`UnsupportedOperationException("operator X does not support reconfiguration")`
instead of returning `Failure`, and every production override returns `Success`
or throws. So for an operator that advertises `supportReconfiguration = true`
without an override (`PythonLambdaFunctionOpDesc`,
`HuggingFaceSentimentAnalysisOpDesc`, `PythonUDFSourceOpDescV2`,
`DummyOpDesc`), the exception escaped the service, reached the websocket
catch-all in `WorkflowWebsocketResource`, and was recorded as a
`COMPILATION_ERROR` fatal error on "unknown operator". The frontend maps that
`WorkflowErrorEvent` to `ExecutionState.Failed`.
```
Before: Confirm Change on Python Lambda while paused ->
UnsupportedOperationException escapes -> COMPILATION_ERROR -> canvas shows
Failed, Kill disabled, Run offered; engine still PAUSED
(workflow_executions.status = 2)
After: Confirm Change on Python Lambda while paused ->
ModifyLogicResponse(isValid = false, "operator PythonLambdaFunctionOpDesc does
not support reconfiguration") -> error notification shown, run stays paused,
Resume still works
```
Screenshots from the reproduction on `adc6a4ac4` (Text Input -> Python UDF
yielding once per second -> Python Lambda, run, pause, unlock the Python
Lambda, change its expression, confirm):
| Before: paused, "Unlock for Logic Change" enabled | Before: after "Confirm
Change", run shown as Failed |
| --- | --- |
| <img width="1263" height="799"
alt="01-paused-unlock-enabled-python-lambda"
src="https://github.com/user-attachments/assets/7e6918c1-5036-43a0-95e2-ce7d84894bec"
/> | <img width="1263" height="799"
alt="02-after-confirm-change-run-shown-failed"
src="https://github.com/user-attachments/assets/80ae2fe1-60d8-4c98-9c45-a4f063bc2c3f"
/> |
| Before: the error, visible only with no operator selected | Before:
expanded stack trace |
| --- | --- |
| <img width="1263" height="799"
alt="04-static-error-compilation-error-unsupported-reconfiguration"
src="https://github.com/user-attachments/assets/964bd200-1e98-424e-9fd3-9c67e1a4139f"
/> | <img width="1263" height="799" alt="05-static-error-expanded-stack-trace"
src="https://github.com/user-attachments/assets/883390d0-a19a-4099-b81a-1ba332f1d3c9"
/> |
With this branch, the same steps: "Confirm Change" is answered with an error
notification reading `operator PythonLambdaFunctionOpDesc does not support
reconfiguration`, the button still reads "Resume", Kill stays enabled, the
execution row stays at PAUSED (status 2), `computing-unit-master.log` logs no
websocket error, and Resume continues the same execution.
| After: error notification, run still paused | After: Resume continues the
same run |
| --- | --- |
| <img width="1263" height="799"
alt="07-after-fix-error-notification-run-stays-paused"
src="https://github.com/user-attachments/assets/31488856-e2e1-4ae5-8e82-f5ce11ba2332"
/> | <img width="1263" height="799"
alt="08-after-fix-resume-continues-same-run"
src="https://github.com/user-attachments/assets/1b43abed-b431-4ab1-a467-73862243ebcf"
/> |
`computing-unit-master.log` at that moment, showing the engine still paused
after the error:
```
[2026-10-10 20:19:12,136] [ERROR]
[org.apache.texera.web.resource.WorkflowWebsocketResource] [dw-56] - error
occurred in websocket
java.lang.UnsupportedOperationException: operator PythonLambdaFunctionOpDesc
does not support reconfiguration
at
org.apache.texera.amber.operator.LogicalOp.runtimeReconfiguration(LogicalOp.scala:495)
at
org.apache.texera.web.service.ExecutionReconfigurationService.modifyOperatorLogic(ExecutionReconfigurationService.scala:80)
at
org.apache.texera.web.resource.WorkflowWebsocketResource.myOnMsg(WorkflowWebsocketResource.scala:110)
...
[2026-10-10 20:19:12,152] [INFO]
[org.apache.texera.web.WorkflowLifecycleManager] [dw-56] -
[workflowId=WorkflowIdentity(5)] workflow state clean up postponed. current
user count = 1, workflow status = PAUSED
```
**Changes.**
1. `ExecutionReconfigurationService.modifyOperatorLogic` wraps the
`runtimeReconfiguration` call in `Try(...).flatten`. A thrown exception now
takes the same path as a returned `Failure`: the response is
`ModifyLogicResponse(opId, isValid = false, reason)`, nothing is appended to
the pending reconfiguration queue, and the run stays paused. This also covers
an override that throws while rebuilding its executor, for example from
`getPhysicalOp`. `Try` catches `NonFatal` only, so an `InterruptedException` or
a `VirtualMachineError` still propagates. The reason is the exception message,
or the exception's string form when it has no message, so the notification is
never empty.
2. No operator flag changes. The three operators above still show "Unlock
for Logic Change" while paused; confirming an edit on them now shows "operator
... does not support reconfiguration" as an error notification instead of
failing the run. Whether those operators should stop advertising
`supportReconfiguration`, or get a real override, is a separate decision and is
left out of this PR.
### Any related issues, documentation, discussions?
Closes #8967
### How was this PR tested?
Five new tests in
`amber/src/test/scala/org/apache/texera/web/service/ExecutionReconfigurationServiceSpec.scala`,
each of which fails without the change it covers:
- `modifyOperatorLogic should report an operator without a reconfiguration
override as a rejected edit`: the deployed operator is a real `DummyOpDesc`, so
the test goes through the `LogicalOp` base implementation that throws. Expects
`isValid = false`, a message containing `DummyOpDesc does not support
reconfiguration`, and a pending queue that still holds only the edit queued
before the request.
- `modifyOperatorLogic should report an operator whose reconfiguration
override throws as a rejected edit`: a test-only descriptor whose override
throws `IllegalStateException`. Expects `isValid = false`, the exception
message, and the same untouched queue.
- `modifyOperatorLogic should describe a rejection by its exception type
when the exception has no message`: the override throws `new
IllegalStateException()`; the response carries
`java.lang.IllegalStateException` rather than null.
- `modifyOperatorLogic should let a fatal reconfiguration exception escape
instead of reporting a rejection`: the override throws `InterruptedException`;
`modifyOperatorLogic` throws it rather than answering with a rejection.
- `modifyOperatorLogic should accept a valid edit after a rejected one`: a
rejected edit on a `DummyOpDesc` followed by a valid edit on a `LimitOpDesc` in
the same workflow; the second is accepted and is the only entry in the pending
queue.
The doc comment on the existing `RejectingReconfigurationOpDesc` no longer
claims the `Failure` arm needs a test-only descriptor.
```
sbt "WorkflowExecutionService/testOnly
org.apache.texera.web.service.ExecutionReconfigurationServiceSpec"
# Tests: succeeded 23, failed 0
sbt "WorkflowOperator/testOnly
org.apache.texera.amber.operator.filter.FilterOpDescSpec
org.apache.texera.amber.operator.map.MapOpDescSpec"
# 12 tests, 0 failures: the two "propagate exceptions from
newOpDesc.getPhysicalOp" tests still pass, so overrides keep throwing and the
service now catches
sbt "WorkflowExecutionService/scalafmtCheck"
"WorkflowExecutionService/Test/scalafmtCheck"
# clean
```
Mutation check: removing the `Try(...).flatten` wrap fails the two
rejected-edit tests, the no-message test and the accept-after-rejection test,
while the fatal-exception test keeps passing; replacing the message fallback
with a bare `exception.getMessage` fails only the no-message test.
Manual check in Chrome against a build of this branch on the local stack
with a local computing unit:
1. Workflow: Text Input -> Python UDF whose `process_tuple` yields the input
once per second for 900 seconds -> Python Lambda Function.
2. Click Run, wait until the UDF is producing output, click Pause. The
button reads "Resume".
3. Select the Python Lambda Function, click "Unlock for Logic Change",
change the expression, click "Confirm Change".
4. An error notification shows `operator PythonLambdaFunctionOpDesc does not
support reconfiguration`; the button still reads "Resume" and Kill stays
enabled; `workflow_executions` keeps status 2 (PAUSED); the master log has no
"error occurred in websocket" entry. Click Resume: the same execution continues
and the UDF's output count keeps climbing. On main the same steps showed the
run as Failed (screenshots above).
### Was this PR authored or co-authored using generative AI tooling?
Yes. Generated-by: Claude Code (claude-fable-5-1). Co-authored with Claude;
the author reviewed the change before submission.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]