mirror of
https://github.com/nosqlbench/nosqlbench.git
synced 2025-02-25 18:55:28 -06:00
add generalized error handling in http
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user