Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for issue #332 (Hystrix Javanica Error Propagation does not work cor... #334

Merged
merged 1 commit into from
Nov 11, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions hystrix-contrib/hystrix-javanica/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,24 @@ apply plugin: 'idea'
aspectjVersion = '1.7.4'

dependencies {
compile project(':hystrix-core')
compile project(':hystrix-core')

compile "org.aspectj:aspectjweaver:$aspectjVersion"
compile "org.aspectj:aspectjrt:$aspectjVersion"
compile "org.aspectj:aspectjweaver:$aspectjVersion"
compile "org.aspectj:aspectjrt:$aspectjVersion"

compile 'com.google.guava:guava:15.0'
compile 'commons-collections:commons-collections:3.2.1'
compile 'org.apache.commons:commons-lang3:3.1'
compile 'com.google.code.findbugs:jsr305:2.0.0'
testCompile 'junit:junit-dep:4.10'
testCompile 'org.springframework:spring-core:4.0.0.RELEASE'
testCompile 'org.springframework:spring-context:4.0.0.RELEASE'
testCompile 'org.springframework:spring-aop:4.0.0.RELEASE'
testCompile 'org.springframework:spring-test:4.0.0.RELEASE'
testCompile 'cglib:cglib:3.1'
testCompile 'org.mockito:mockito-all:1.9.5'
compile 'com.google.guava:guava:15.0'
compile 'commons-collections:commons-collections:3.2.1'
compile 'org.apache.commons:commons-lang3:3.1'
compile 'com.google.code.findbugs:jsr305:2.0.0'
testCompile 'junit:junit-dep:4.10'
testCompile 'org.springframework:spring-core:4.0.0.RELEASE'
testCompile 'org.springframework:spring-context:4.0.0.RELEASE'
testCompile 'org.springframework:spring-aop:4.0.0.RELEASE'
testCompile 'org.springframework:spring-test:4.0.0.RELEASE'
testCompile 'cglib:cglib:3.1'
testCompile 'org.mockito:mockito-all:1.9.5'
testCompile 'log4j:log4j:1.2.17'
testCompile 'org.slf4j:slf4j-log4j12:1.7.7'
}

eclipse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.netflix.hystrix.contrib.javanica.collapser;

import com.google.common.base.Optional;
import com.netflix.hystrix.HystrixCollapser;
import com.netflix.hystrix.HystrixCollapserKey;
import com.netflix.hystrix.HystrixCommand;
Expand All @@ -33,7 +34,7 @@
* Collapses multiple requests into a single {@link HystrixCommand} execution based
* on a time window and optionally a max batch size.
*/
public class CommandCollapser extends HystrixCollapser<List<Object>, Object, Object> {
public class CommandCollapser extends HystrixCollapser<List<Optional<Object>>, Object, Object> {

private MetaHolder metaHolder;

Expand Down Expand Up @@ -69,7 +70,7 @@ public Object getRequestArgument() {
* Creates batch command.
*/
@Override
protected HystrixCommand<List<Object>> createCommand(
protected HystrixCommand<List<Optional<Object>>> createCommand(
Collection<CollapsedRequest<Object, Object>> collapsedRequests) {
BatchHystrixCommand command = BatchHystrixCommandFactory.getInstance().create(metaHolder, collapsedRequests);
command.setFallbackEnabled(metaHolder.getHystrixCollapser().fallbackEnabled());
Expand All @@ -80,14 +81,17 @@ protected HystrixCommand<List<Object>> createCommand(
* {@inheritDoc}
*/
@Override
protected void mapResponseToRequests(List<Object> batchResponse,
protected void mapResponseToRequests(List<Optional<Object>> batchResponse,
Collection<CollapsedRequest<Object, Object>> collapsedRequests) {
if (batchResponse.size() < collapsedRequests.size()) {
throw new RuntimeException(createMessage(collapsedRequests, batchResponse));
}
int count = 0;
for (CollapsedRequest<Object, Object> request : collapsedRequests) {
request.setResponse(batchResponse.get(count++));
Optional response = batchResponse.get(count++);
if (response.isPresent()) { // allows prevent IllegalStateException
request.setResponse(response.get());
}
}
}

Expand Down Expand Up @@ -116,7 +120,7 @@ public Setter build() {
}

private String createMessage(Collection<CollapsedRequest<Object, Object>> requests,
List<Object> response) {
List<Optional<Object>> response) {
return ERROR_MSG + arrayFormat(ERROR_MSF_TEMPLATE, new Object[]{getCollapserKey().name(), requests.size(), response.size()}).getMessage();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import com.google.common.base.Throwables;
import com.netflix.hystrix.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException;
import com.netflix.hystrix.exception.HystrixBadRequestException;
import com.netflix.hystrix.exception.HystrixRuntimeException;

import javax.annotation.concurrent.ThreadSafe;
import java.util.Collection;
Expand Down Expand Up @@ -136,12 +138,18 @@ protected String getCacheKey() {
return key;
}

/**
* Check whether triggered exception is ignorable.
*
* @param throwable the exception occurred during a command execution
* @return true if exception is ignorable, otherwise - false
*/
boolean isIgnorable(Throwable throwable) {
if (ignoreExceptions == null || ignoreExceptions.length == 0) {
return false;
}
for (Class<? extends Throwable> ignoreException : ignoreExceptions) {
if (throwable.getClass().isAssignableFrom(ignoreException)) {
if (ignoreException.isAssignableFrom(throwable.getClass())) {
return true;
}
}
Expand All @@ -150,20 +158,28 @@ boolean isIgnorable(Throwable throwable) {

/**
* Executes an action. If an action has failed and an exception is ignorable then propagate it as HystrixBadRequestException
* otherwise propagate it as RuntimeException to trigger fallback method.
* otherwise propagate original exception to trigger fallback method.
* Note: If an exception occurred in a command directly extends {@link java.lang.Throwable} then this exception cannot be re-thrown
* as original exception because HystrixCommand.run() allows throw subclasses of {@link java.lang.Exception}.
* Thus we need to wrap cause in RuntimeException, anyway in this case the fallback logic will be triggered.
*
* @param action the action
* @return result of command action execution
*/
Object process(Action action) throws RuntimeException {
Object process(Action action) throws Exception {
Object result;
try {
result = action.execute();
} catch (Throwable throwable) {
if (isIgnorable(throwable)) {
throw new HystrixBadRequestException(throwable.getMessage(), throwable);
} catch (CommandActionExecutionException throwable) {
Throwable cause = throwable.getCause();
if (isIgnorable(cause)) {
throw new HystrixBadRequestException(cause.getMessage(), cause);
}
if (cause instanceof Exception) {
throw (Exception) cause;
} else {
throw Throwables.propagate(cause);
}
throw Throwables.propagate(throwable);
}
return result;
}
Expand All @@ -186,7 +202,54 @@ protected T getFallback() {
* Common action.
*/
abstract class Action {
abstract Object execute();
/**
* Each implementation of this method should wrap any exceptions in CommandActionExecutionException.
*
* @return execution result
* @throws CommandActionExecutionException
*/
abstract Object execute() throws CommandActionExecutionException;
}


/**
* Builder to create error message for failed fallback operation.
*/
static class FallbackErrorMessageBuilder {
private StringBuilder builder = new StringBuilder("failed to processed fallback");

static FallbackErrorMessageBuilder create() {
return new FallbackErrorMessageBuilder();
}

public FallbackErrorMessageBuilder append(CommandAction action, Throwable throwable) {
return commandAction(action).exception(throwable);
}

private FallbackErrorMessageBuilder commandAction(CommandAction action) {
if (action instanceof CommandExecutionAction || action instanceof LazyCommandExecutionAction) {
builder.append(": '").append(action.getActionName()).append("'. ")
.append(action.getActionName()).append(" fallback is a hystrix command. ");
} else if (action instanceof MethodExecutionAction) {
builder.append(" is the method: '").append(action.getActionName()).append("'. ");
}
return this;
}

private FallbackErrorMessageBuilder exception(Throwable throwable) {
if (throwable instanceof HystrixBadRequestException) {
builder.append("exception: '").append(throwable.getCause().getClass())
.append("' occurred in fallback was ignored and wrapped to HystrixBadRequestException.\n");
} else if (throwable instanceof HystrixRuntimeException) {
builder.append("exception: '").append(throwable.getCause().getClass())
.append("' occurred in fallback wasn't ignored.\n");
}
return this;
}

public String build() {
return builder.toString();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
package com.netflix.hystrix.contrib.javanica.command;


import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.netflix.hystrix.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.exception.FallbackInvocationException;
import com.netflix.hystrix.exception.HystrixBadRequestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.concurrent.ThreadSafe;
import java.util.Collection;
Expand All @@ -29,7 +33,9 @@
* This command is used in collapser.
*/
@ThreadSafe
public class BatchHystrixCommand extends AbstractHystrixCommand<List<Object>> {
public class BatchHystrixCommand extends AbstractHystrixCommand<List<Optional<Object>>> {

private static final Logger LOGGER = LoggerFactory.getLogger(GenericCommand.class);

/**
* If some error occurs during the processing in run() then if {@link #fallbackEnabled} is true then the {@link #processWithFallback}
Expand Down Expand Up @@ -63,22 +69,21 @@ public void setFallbackEnabled(boolean fallbackEnabled) {
* {@inheritDoc}
*/
@Override
protected List<Object> run() throws Exception {
List<Object> response = Lists.newArrayList();
protected List<Optional<Object>> run() throws Exception {
List<Optional<Object>> response = Lists.newArrayList();
for (HystrixCollapser.CollapsedRequest<Object, Object> request : getCollapsedRequests()) {
final Object[] args = (Object[]) request.getArgument();
try {
response.add(fallbackEnabled ? processWithFallback(args) : process(args));
} catch (RuntimeException ex) {
response.add(Optional.of(fallbackEnabled ? processWithFallback(args) : process(args)));
} catch (Exception ex) {
request.setException(ex);
response.add(null);
response.add(Optional.absent());
}

}
return response;
}

private Object process(final Object[] args) {
private Object process(final Object[] args) throws Exception {
return process(new Action() {
@Override
Object execute() {
Expand All @@ -87,29 +92,44 @@ Object execute() {
});
}

private Object processWithFallback(final Object[] args) {
Object result = null;
private Object processWithFallback(final Object[] args) throws Exception {
Object result;
try {
result = process(args);
} catch (RuntimeException ex) {
if (ex instanceof HystrixBadRequestException || getFallbackAction() == null) {
} catch (Exception ex) {
if (ex instanceof HystrixBadRequestException) {
throw ex;
} else {
if (getFallbackAction() != null) {
result = processFallback(args);
} else {
// if command doesn't have fallback then
// call super.getFallback() that throws exception by default.
result = super.getFallback();
}
}
}
return result;
}

private Object processFallback(final Object[] args) {
return process(new Action() {
@Override
Object execute() {
return getFallbackAction().executeWithArgs(ExecutionType.SYNCHRONOUS, args);
if (getFallbackAction() != null) {
final CommandAction commandAction = getFallbackAction();
try {
return process(new Action() {
@Override
Object execute() {
return commandAction.executeWithArgs(ExecutionType.SYNCHRONOUS, args);
}
});
} catch (Throwable e) {
LOGGER.error(FallbackErrorMessageBuilder.create()
.append(commandAction, e).build());
throw new FallbackInvocationException(e.getCause());
}
});
} else {
return super.getFallback();
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,37 @@
*/
package com.netflix.hystrix.contrib.javanica.command;

import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException;

/**
* Simple action to encapsulate some logic to process it in a Hystrix command.
*/
public abstract class CommandAction {

public abstract Object execute(ExecutionType executionType);
/**
* Executes action in accordance with the given execution type.
*
* @param executionType the execution type
* @return result of execution
* @throws com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException
*/
public abstract Object execute(ExecutionType executionType) throws CommandActionExecutionException;

/**
* Executes action with parameters in accordance with the given execution ty
*
* @param executionType the execution type
* @param args the parameters of the action
* @return result of execution
* @throws com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException
*/
public abstract Object executeWithArgs(ExecutionType executionType, Object[] args) throws CommandActionExecutionException;

public abstract Object executeWithArgs(ExecutionType executionType, Object[] args);
/**
* Gets action name. Useful for debugging.
*
* @return the action name
*/
public abstract String getActionName();

}
Loading