Throwing an unhandled BpmnError in the signal() method of a AbstractBpmnActivityBehavior gets lost

I’m using AbstractBpmnActivityBehavior to make a RPC call over our Service Bus.

I did notice that when an error is returned from the Backend Service that we would like to handle in the BPMN process, I can’t just always throw a BpmnError(). This works fine when we have a Error Boundary Event on the ServiceTask, but when we don’t have one there, the Error is just lost and the process just waits.

I do have enableExceptionsAfterUnhandledBpmnError enabled, but this doesn’t seem to work when throwing a BpmnError in the signal() phase of the AbstractBpmnActivityBehavior.

The workaround I do now is to use getBpmnModelElementInstance() and check if there is a matching Error Boundary Event on the current activity, and throw the BpmnError in case one is found, otherwise, we create an incident.

Am I missing something? I tried a long time to make this work with a JavaDelegate (since that seems to be preferred over a AbstractBpmnActivityBehavior), but since we need to block the ServiceTask for the duration where we are waiting for the reply, this didn’t scale well. I could only do this by blocking the Thread untill we got back a reply.

Simular error: AbstractBpmnActivityBehavior - thow error in signal

In case someone is interested in my code to find a relevant Error Boundary Event:

  /**
   * Find a Boundary Event on the given execution that matches the specified errorCode.
   *
   * @param execution
   * @param errorCode
   * @return
   */
  private BoundaryEvent findBoundaryEventByErrorCode(ActivityExecution execution, String errorCode) {
    if (errorCode != null) {
      Collection<BoundaryEvent> boundaryEvents = execution.getBpmnModelElementInstance().getParentElement().getChildElementsByType(BoundaryEvent.class);
      for (BoundaryEvent boundaryEvent : boundaryEvents) {
        Collection<EventDefinition> eventDefinitions = boundaryEvent.getEventDefinitions();
        for (EventDefinition eventDefinition : eventDefinitions) {
          if (eventDefinition instanceof ErrorEventDefinition) {
            ErrorEventDefinition errorEventDefinition = (ErrorEventDefinition) eventDefinition;
            if (errorEventDefinition.getError() != null) {
              String errorEventDefinitionErrorCode = errorEventDefinition.getError().getErrorCode();
              if (errorEventDefinitionErrorCode == null || errorCode.equals(errorEventDefinitionErrorCode)) {
                return boundaryEvent;
              }
            }
          }
        }
      }
    }
    return null;
  }

Can you explain a little bit about what you’re trying to achieve with using the signal() call

In the execute(), I make the actual call to the service, and I register for a callback when a reply is received.

When I receive the callback a bit later, I use runtimeService.signal(rpcSession.getExecutionId(), SendRpc.STATUS_OK, messageString, null); so I end up in the signal() code with an actual ActivityExecution object.

In the signal() method, I can then use the signalName to notify the engine if the result was OK, FAILED or has a TIMEOUT.

This is my actual signal() code:

  public void signal(ActivityExecution execution, String signalName, Object signalData) {
    if (signalName.equals(STATUS_OK)) {
      log.debug("[{}] Completing execution {} with variables {}", execution.getProcessInstanceId(), execution.getId(), signalData);
      SpinJsonNode messageJson = Spin.JSON(signalData);

      execution.setVariableLocal("response", messageJson);
      leave(execution);

    } else if (signalName.equals(STATUS_ERROR)) {
      log.debug("[{}] Marking execution {} as errored with error {}", execution.getProcessInstanceId(), execution.getId(), signalData);
      SpinJsonNode messageJson = Spin.JSON(signalData);

      execution.setVariableLocal("response", messageJson);

      try {
        String errorCode = messageJson.jsonPath("$.errors[0].code").stringValue();

        // We can't simply throw a BpmnError here, because when there is no Error Boundary Event defined, our
        // error will be lost. We need to look ourselves for a Boundary Event that matches the error code.
        // Setting enableExceptionsAfterUnhandledBpmnError doesn't seem to influence this, unfortunately.
        // See https://forum.camunda.io/t/abstractbpmnactivitybehavior-thow-error-in-signal/8882

        BoundaryEvent boundaryEvent = findBoundaryEventByErrorCode(execution, errorCode);
        if (boundaryEvent != null) {
          log.debug("[{}] Boundary event found for error code: {}", execution.getProcessInstanceId(), errorCode);
          throw new BpmnError(errorCode, (String) signalData);
        } else {
          log.debug("[{}] No boundary event found for error code: {}. Creating incident.", execution.getProcessInstanceId(), errorCode);
          execution.createIncident(signalName, execution.getActivityInstanceId(), (String) signalData);
        }

      } catch (SpinRuntimeException e) {
        // We couldn't get the error code from the JSON, so we can only create an incident
        log.warn("[{}] Error code not found. Creating incident.", execution.getProcessInstanceId());
        execution.createIncident(signalName, execution.getActivityInstanceId(), (String) signalData);
      }

    } else if (signalName.equals(STATUS_TIMEOUT)) {
      log.warn("[{}] Marking execution {} as timed out", execution.getProcessInstanceId(), execution.getId());

      execution.createIncident(signalName, execution.getActivityInstanceId());

    } else {
      throw new RuntimeException("Unknown signal name: " + signalName);
    }
  }

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.