add generalized error handling in http

This commit is contained in:
Jonathan Shook
2021-01-21 15:26:35 -06:00
parent fc42ff4736
commit 71c0590301
32 changed files with 893 additions and 56 deletions

View File

@@ -4,6 +4,7 @@ import com.codahale.metrics.Timer;
import io.nosqlbench.activitytype.cmds.HttpOp;
import io.nosqlbench.activitytype.cmds.ReadyHttpOp;
import io.nosqlbench.engine.api.activityapi.core.SyncAction;
import io.nosqlbench.engine.api.activityapi.errorhandling.modular.ErrorDetail;
import io.nosqlbench.engine.api.activityapi.planning.OpSequence;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import io.nosqlbench.nb.api.errors.BasicError;
@@ -26,7 +27,7 @@ public class HttpAction implements SyncAction {
private final HttpActivity httpActivity;
private final int slot;
private final int maxTries = 1;
private int maxTries = 1;
private OpSequence<ReadyHttpOp> sequencer;
private HttpClient client;
@@ -42,6 +43,7 @@ public class HttpAction implements SyncAction {
public void init() {
this.sequencer = httpActivity.getSequencer();
this.client = initClient(httpActivity.getClientScope());
this.maxTries = httpActivity.getActivityDef().getParams().getOptionalInteger("maxtries").orElse(10);
}
private HttpClient initClient(ClientScope clientScope) {
@@ -50,6 +52,7 @@ public class HttpAction implements SyncAction {
@Override
public int runCycle(long cycleValue) {
int tries = 0;
// The request to be used must be constructed from the template each time.
HttpOp httpOp = null;
@@ -64,7 +67,7 @@ public class HttpAction implements SyncAction {
if (httpActivity.isDiagnosticMode()) {
if (httpOp != null) {
httpActivity.console.summarizeRequest("ERRORED REQUEST", e, httpOp.request, System.out, cycleValue,
System.nanoTime());
System.nanoTime());
} else {
System.out.println("---- REQUEST was null");
}
@@ -73,7 +76,6 @@ public class HttpAction implements SyncAction {
} finally {
}
int tries = 0;
while (tries < maxTries) {
tries++;
@@ -84,31 +86,33 @@ public class HttpAction implements SyncAction {
throw new RuntimeException("while waiting for response in cycle " + cycleValue + ":" + e.getMessage(), e);
}
HttpResponse<String> response=null;
HttpResponse<String> response = null;
long startat = System.nanoTime();
Exception error = null;
try {
response = responseFuture.get(httpActivity.getTimeoutMillis(), TimeUnit.MILLISECONDS);
if (httpOp.ok_status!=null) {
if (httpOp.ok_status != null) {
if (!httpOp.ok_status.matcher(String.valueOf(response.statusCode())).matches()) {
throw new InvalidStatusCodeException(cycleValue, httpOp.ok_status, response.statusCode());
}
}
if (httpOp.ok_body!=null) {
if (httpOp.ok_body != null) {
if (!httpOp.ok_body.matcher(response.body()).matches()) {
throw new InvalidResponseBodyException(cycleValue, httpOp.ok_body, response.body());
}
}
} catch (Exception e) {
error = new RuntimeException("while waiting for response in cycle " + cycleValue + ":" + e.getMessage(), e);
error = e;
// error = new RuntimeException("while waiting for response in cycle " + cycleValue + ":" + e.getMessage(), e);
} finally {
long nanos = System.nanoTime() - startat;
httpActivity.statusCodeHisto.update(response.statusCode());
httpActivity.resultTimer.update(nanos, TimeUnit.NANOSECONDS);
if (error==null) {
if (error == null) {
httpActivity.resultSuccessTimer.update(nanos, TimeUnit.NANOSECONDS);
}
if (httpActivity.isDiagnosticMode()) {
if (response!=null) {
if (response != null) {
httpActivity.console.summarizeResponseChain(null, response, System.out, cycleValue, nanos);
} else {
System.out.println("---- RESPONSE was null");
@@ -116,12 +120,22 @@ public class HttpAction implements SyncAction {
System.out.println();
}
if (error!=null) {
if (error == null) {
break; // break out of the tries loop without retrying, because there was no error
} else {
// count and log exception types
ErrorDetail detail = httpActivity.getErrorHandler().handleError(error, cycleValue, nanos);
if (!detail.isRetryable()) {
break; // break out of the tries loop without retrying, because the error handler said so
}
}
}
}
httpActivity.triesHisto.update(tries);
return 0;
}

View File

@@ -6,6 +6,7 @@ import com.codahale.metrics.Timer;
import io.nosqlbench.activitytype.cmds.ReadyHttpOp;
import io.nosqlbench.engine.api.activityapi.core.Activity;
import io.nosqlbench.engine.api.activityapi.core.ActivityDefObserver;
import io.nosqlbench.engine.api.activityapi.errorhandling.modular.NBErrorHandler;
import io.nosqlbench.engine.api.activityapi.planning.OpSequence;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import io.nosqlbench.engine.api.activityimpl.SimpleActivity;
@@ -32,10 +33,12 @@ public class HttpActivity extends SimpleActivity implements Activity, ActivityDe
public Meter rowCounter;
public Histogram skippedTokens;
public Timer resultSuccessTimer;
public Histogram statusCodeHisto;
private OpSequence<ReadyHttpOp> sequencer;
private boolean diagnosticsEnabled;
private long timeout = Long.MAX_VALUE;
private NBErrorHandler errorhandler;
public HttpActivity(ActivityDef activityDef) {
super(activityDef);
@@ -51,12 +54,21 @@ public class HttpActivity extends SimpleActivity implements Activity, ActivityDe
resultTimer = ActivityMetrics.timer(activityDef, "result");
triesHisto = ActivityMetrics.histogram(activityDef, "tries");
rowCounter = ActivityMetrics.meter(activityDef, "rows");
statusCodeHisto = ActivityMetrics.histogram(activityDef, "statuscode");
skippedTokens = ActivityMetrics.histogram(activityDef, "skipped-tokens");
resultSuccessTimer = ActivityMetrics.timer(activityDef, "result-success");
this.sequencer = createOpSequence(ReadyHttpOp::new);
setDefaultsFromOpSequence(sequencer);
onActivityDefUpdate(activityDef);
this.errorhandler = new NBErrorHandler(
() -> activityDef.getParams().getOptionalString("errors").orElse("stop"),
this::getExceptionMetrics
);
}
public NBErrorHandler getErrorHandler() {
return this.errorhandler;
}
@Override
@@ -64,8 +76,8 @@ public class HttpActivity extends SimpleActivity implements Activity, ActivityDe
super.onActivityDefUpdate(activityDef);
this.console = getParams().getOptionalString("diag")
.map(s -> HttpConsoleFormats.apply(s, this.console))
.orElseGet(() -> HttpConsoleFormats.apply(null, null));
.map(s -> HttpConsoleFormats.apply(s, this.console))
.orElseGet(() -> HttpConsoleFormats.apply(null, null));
this.diagnosticsEnabled = console.isDiagnosticMode();

View File

@@ -144,23 +144,34 @@ All other request fields are optional and have reasonable defaults:
against the string form of a status code. If any characters other than digits spaces and commas
are found in this value, then it is taken as a regex. If this is not provided, then any status
code which is >=200 and <300 is considered valid.
- **ok-body** - An optional regex pattern which will be applied to the body to verify that it is a
valid response. If this is not provided, then content bodies are read, but any content is
considered valid.
- **ok-body** - An optional regex pattern which will be applied to the
body to verify that it is a valid response. If this is not provided,
then content bodies are read, but any content is considered valid.
Any other statement parameter which is capitalized is taken as a request header. If additional
fields are provided which are not included in the above list, or which are not capitalized, then an
error is thrown.
Any other statement parameter which is capitalized is taken as a request
header. If additional fields are provided which are not included in the
above list, or which are not capitalized, then an error is thrown.
## Error Handling & Retries
Presently, no determination is made about whether or not an errored response *should* be retryable.
More contextual error handling may be added in a future version.
By default, a request which encounters an exception is retried up to 10
times. If you want to change this, set another value to the
`retries=` activity parameters.
Presently, no determination is made about whether or not an errored
response *should* be retryable, but it is possible to configure this if
you have a specific exception type that indicates a retryable operation.
The HTTP driver is the first NB driver to include a completely
configurable error handler chain. This is explained in the
`error-handling` topic. By default, the HTTP activity's error handler is
wired to stop the activity for any error encountered. For more details see
the `error-handling` topic.
## SSL Support
SSL should work for any basic client request that doesn't need custom SSL configuration. If needed,
more configurable SSL support will be added.
SSL should work for any basic client request that doesn't need custom SSL
configuration. If needed, more configurable SSL support will be added.
## Client Behavior