Skip to content

Commit

Permalink
Merge 94ea98e into 78a48a3
Browse files Browse the repository at this point in the history
  • Loading branch information
efonsell committed Feb 6, 2021
2 parents 78a48a3 + 94ea98e commit 1e0e14d
Show file tree
Hide file tree
Showing 21 changed files with 841 additions and 96 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,28 @@
**Highlights**
- Support updating workflow instance business key.
- Support for searching workflow instances by state variable key and value.
- Control retrying and logging of an exception thrown by a state method via `WorkflowSettings` (replaces deprecated `WorkflowState.isRetryAllowed(...)`).
- Control logging and sleeping after exceptions in `WorkflowDispatcher`.
- Control logging and sleeping after a failure to save workflow instance state.

**Details**
- `nflow-engine`
- `WorkflowInstanceService.updateWorkflowInstance` can now be used to update business key of the workflow instance.
- Use `QueryWorkflowInstances.setStateVariable` to limit search query by state variable name and key. Only the latest value of the state variable of the workflow instance is used.
- Control retrying and logging of an exception thrown by a state method via `WorkflowSettings.Builder.setExceptionAnalyzer(...)` / `ExceptionHandling`:
- Control whether the exception is considered retryable or not (replaces deprecated `WorkflowState.isRetryAllowed(...)`).
- Control which log level is used to log the retryable exception.
- Control whether the stack trace of the retryable exception is logged or not.
- Control logging and sleeping after exceptions in `WorkflowDispatcher` by providing a `DispatcherExceptionHandler` that returns `DispatcherExceptionHandling` objects:
- Control whether the exception is logged or not.
- Control which log level is used to log the exception.
- Control whether the stack trace of the exception is logged or not.
- Control whether dispatcher should sleep after the exception or not.
- Control whether the sleep time should be randomized or not.
- Control logging after a failure to save workflow instance state by providing a `StateSaveExceptionHandler` that returns `StateSaveExceptionHandling` objects:
- Control which log level is used to log the exception.
- Control whether the stack trace of the exception is logged or not.
- Control how long the `WorkflowStateProcessor` should sleep before retrying.
- `nflow-rest-api-common`, `nflow-rest-api-jax-rs`, `nflow-rest-api-spring-web`
- `UpdateWorkflowInstanceRequest.businessKey` field was added to support updating workflow instance business key via REST API.
- Added support for new query parameters `stateVariableKey` and `stateVariableValue` to `GET /v1/workflow-instance` to limit search query by state variable name and key. Only the latest value of the state variable of the workflow instance is used.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.nflow.engine.exception;

import static org.slf4j.LoggerFactory.getLogger;

import org.slf4j.Logger;
import org.slf4j.event.Level;
import org.springframework.stereotype.Component;

import io.nflow.engine.exception.DispatcherExceptionHandling.Builder;
import io.nflow.engine.internal.dao.PollingBatchException;
import io.nflow.engine.internal.dao.PollingRaceConditionException;

/**
* Dispatcher exception analyzer analyzes exceptions thrown by the workflow dispatcher and determines how the exception is
* handled.
*/
@Component
public class DispatcherExceptionAnalyzer {

private static final Logger logger = getLogger(DispatcherExceptionAnalyzer.class);

/**
* Analyze the exception.
*
* @param e
* The exception to be analyzed.
* @return How the exception should be handled.
*/
public final DispatcherExceptionHandling analyzeSafely(Exception e) {
try {
return analyze(e);
} catch (Exception analyzerException) {
logger.error("Failed to analyze exception, using default handling.", analyzerException);
}
return getDefultHandling(e);
}

/**
* Override this to provide custom handling.
*
* @param e
* The exception to be analyzed.
* @return How the exception should be handled.
*/
protected DispatcherExceptionHandling analyze(Exception e) {
return getDefultHandling(e);
}

private DispatcherExceptionHandling getDefultHandling(Exception e) {
Builder builder = new DispatcherExceptionHandling.Builder();
if (e instanceof PollingRaceConditionException) {
builder.setLogLevel(Level.DEBUG).setLogStackTrace(false).setRandomizeSleep(true);
} else if (e instanceof PollingBatchException) {
builder.setLogLevel(Level.WARN).setLogStackTrace(false).setSleep(false);
} else if (e instanceof InterruptedException) {
builder.setLog(false).setSleep(false);
}
return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.nflow.engine.exception;

/**
* Controls how an exception should be handled by the dispatcher.
*/
public class DispatcherExceptionHandling extends ExceptionHandling {
/**
* True when dispatcher should log the exception.
*/
public final boolean log;
/**
* True when dispatcher should sleep a while after exception.
*/
public final boolean sleep;
/**
* True when the sleep time should be randomized.
*/
public final boolean randomizeSleep;

DispatcherExceptionHandling(Builder builder) {
super(builder);
this.log = builder.log;
this.sleep = builder.sleep;
this.randomizeSleep = builder.randomizeSleep;
}

/**
* Builder for exception handling settings.
*/
public static class Builder extends ExceptionHandling.Builder<Builder> {
boolean log = true;
boolean sleep = true;
boolean randomizeSleep = false;

/**
* {@inheritDoc}
*/
@Override
public Builder getThis() {
return this;
}

/**
* Set if dispatcher should log the exception or not. Default is true.
*
* @param log
* True if dispatcher should log the exception.
* @return This.
*/
public Builder setLog(boolean log) {
this.log = log;
return this;
}

/**
* Set if dispatcher should sleep a while after exception or not. Default is true.
*
* @param sleep
* True if dispatcher should sleep a while after exception.
* @return This.
*/
public Builder setSleep(boolean sleep) {
this.sleep = sleep;
return this;
}

/**
* Set if sleep time should be randomized or not. Default is false.
*
* @param randomizeSleep
* True if sleep time should be randomized.
* @return This.
*/
public Builder setRandomizeSleep(boolean randomizeSleep) {
this.randomizeSleep = randomizeSleep;
return this;
}

/**
* Create the dispatcher exception handling object.
*
* @return Dispatcher exception handling.
*/
@Override
public DispatcherExceptionHandling build() {
return new DispatcherExceptionHandling(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.nflow.engine.exception;

import static org.slf4j.event.Level.ERROR;

import org.slf4j.event.Level;

/**
* Controls how an exception should be handled.
*/
public class ExceptionHandling {
/**
* The log entry level for logging the exception.
*/
public final Level logLevel;
/**
* True when the exception stack trace should be logged.
*/
public final boolean logStackTrace;

ExceptionHandling(Builder<?> builder) {
this.logLevel = builder.logLevel;
this.logStackTrace = builder.logStackTrace;
}

/**
* Builder for exception handling settings.
*/
public abstract static class Builder<T extends Builder<T>> {
Level logLevel = ERROR;
boolean logStackTrace = true;

/**
* Return this.
*
* @return This.
*/
public abstract T getThis();

/**
* Set the log entry level. Default is ERROR.
*
* @param logLevel
* The log entry level.
* @return This.
*/
public T setLogLevel(Level logLevel) {
this.logLevel = logLevel;
return getThis();
}

/**
* Set if exception stack trace should be logged or not. Default is true.
*
* @param logStackTrace
* True to log the exception stack trace, false to log the exception message only.
* @return This.
*/
public T setLogStackTrace(boolean logStackTrace) {
this.logStackTrace = logStackTrace;
return getThis();
}

/**
* Create the exception handling object.
*
* @return Exception handling.
*/
public ExceptionHandling build() {
return new ExceptionHandling(getThis());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.nflow.engine.exception;

/**
* Controls how an exception thrown by a state method should be handled by the workflow state processor.
*/
public class StateProcessExceptionHandling extends ExceptionHandling {
/**
* True when the state method processing should be retried.
*/
public final boolean isRetryable;

StateProcessExceptionHandling(Builder builder) {
super(builder);
this.isRetryable = builder.isRetryable;
}

/**
* Builder for exception handling settings.
*/
public static class Builder extends ExceptionHandling.Builder<Builder> {
boolean isRetryable = true;

/**
* {@inheritDoc}
*/
@Override
public Builder getThis() {
return this;
}

/**
* Set if state method processing is retryable or not.
*
* @param isRetryable
* True if state method processing should be retried.
* @return This.
*/
public Builder setRetryable(boolean isRetryable) {
this.isRetryable = isRetryable;
return this;
}

/**
* Create the exception handling object.
*
* @return State process exception handling.
*/
@Override
public StateProcessExceptionHandling build() {
return new StateProcessExceptionHandling(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.nflow.engine.exception;

import static org.joda.time.Duration.standardSeconds;
import static org.slf4j.LoggerFactory.getLogger;

import javax.inject.Inject;

import org.joda.time.Duration;
import org.slf4j.Logger;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

/**
* State save exception analyzer analyzes exceptions thrown while trying to save workflow state and determines how the exception
* is handled.
*/
@Component
public class StateSaveExceptionAnalyzer {

private static final Logger logger = getLogger(StateSaveExceptionAnalyzer.class);

private final StateSaveExceptionHandling handling;

/**
* Create state save exception analyzer.
*
* @param env
* The Spring environment.
*/
@Inject
public StateSaveExceptionAnalyzer(Environment env) {
Duration retryDelay = standardSeconds(env.getRequiredProperty("nflow.executor.stateSaveRetryDelay.seconds", Long.class));
handling = new StateSaveExceptionHandling.Builder().setRetryDelay(retryDelay).build();
}

/**
* Analyze the exception.
*
* @param e
* The exception to be analyzed.
* @param saveRetryCount
* How many times the saving has been attempted before this attempt.
* @return How the exception should be handled.
*/
public final StateSaveExceptionHandling analyzeSafely(Exception e, int saveRetryCount) {
try {
return analyze(e, saveRetryCount);
} catch (Exception analyzerException) {
logger.error("Failed to analyze exception, using default handling.", analyzerException);
}
return handling;
}

/**
* Override this to provide custom handling.
*
* @param e
* The exception to be analyzed.
* @param saveRetryCount
* How many times the saving has been attempted before this attempt.
* @return How the exception should be handled.
*/
protected StateSaveExceptionHandling analyze(Exception e, int saveRetryCount) {
return handling;
}
}
Loading

0 comments on commit 1e0e14d

Please sign in to comment.