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]

Reply via email to