Skip to content
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
32 changes: 32 additions & 0 deletions docs/content/querying/querying.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,35 @@ For example, if the query ID is `abc123`, the query can be cancelled as follows:
```sh
curl -X DELETE "http://host:port/druid/v2/abc123"
```

Query Errors
------------

If a query fails, you will get an HTTP 500 response containing a JSON object with the following structure:

```json
{
"error" : "Query timeout",
"errorMessage" : "Timeout waiting for task.",
"errorClass" : "java.util.concurrent.TimeoutException",
"host" : "druid1.example.com:8083"
}
```

The fields in the response are:

|field|description|
|-----|-----------|
|error|A well-defined error code (see below).|
|errorMessage|A free-form message with more information about the error. May be null.|
|errorClass|The class of the exception that caused this error. May be null.|
|host|The host on which this error occurred. May be null.|

Possible codes for the *error* field include:

|code|description|
|----|-----------|
|`Query timeout`|The query timed out.|
|`Query interrupted`|The query was interrupted, possibly due to JVM shutdown.|
|`Query cancelled`|The query was cancelled through the query cancellation API.|
|`Unknown exception`|Some other exception occurred. Check errorMessage and errorClass for details, although keep in mind that the contents of those fields are free-form and may change from release to release.|
113 changes: 75 additions & 38 deletions processing/src/main/java/io/druid/query/QueryInterruptedException.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,88 +21,125 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableSet;

import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeoutException;

/**
* Exception representing a failed query. The name "QueryInterruptedException" is a misnomer; this is actually
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we rename this to QueryFailedException or something more meaningful :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe its not renamed to maintain backwards compatibility ?

* used on the client side for *all* kinds of failed queries.
*
* Fields:
* - "errorCode" is a well-defined errorCode code taken from a specific list (see the static constants). "Unknown exception"
* represents all wrapped exceptions other than interrupt/timeout/cancellation.
* - "errorMessage" is the toString of the wrapped exception
* - "errorClass" is the class of the wrapped exception
* - "host" is the host that the errorCode occurred on
*
* The QueryResource is expected to emit the JSON form of this object when errors happen, and the DirectDruidClient
* deserializes and wraps them.
*/
public class QueryInterruptedException extends RuntimeException
{
public static final String QUERY_INTERRUPTED = "Query interrupted";
public static final String QUERY_TIMEOUT = "Query timeout";
public static final String QUERY_CANCELLED = "Query cancelled";
public static final String UNKNOWN_EXCEPTION = "Unknown exception";

private static final Set<String> listKnownException = ImmutableSet.of(
QUERY_CANCELLED,
QUERY_INTERRUPTED,
QUERY_TIMEOUT,
UNKNOWN_EXCEPTION
);

@JsonProperty
private final String causeMessage;
@JsonProperty
private final String errorCode;
private final String errorClass;
private final String host;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this should probably be kept for rolling-update purposes.

causeMessage and errorMessage will have to both be present for at least one version so that rolling updates maintain the correct behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

causeMessage was dropped by the broker previously, so I didn't think it was important to keep it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also wasn't written (the old QueryResource only serialized the message, not the whole exception)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure ok

@JsonCreator
public QueryInterruptedException(
@JsonProperty("error") String message,
@JsonProperty("causeMessage") String causeMessage,
@JsonProperty("error") String errorCode,
@JsonProperty("errorMessage") String errorMessage,
@JsonProperty("errorClass") String errorClass,
@JsonProperty("host") String host
)
{
super(message);
this.causeMessage = causeMessage;
super(errorMessage);
this.errorCode = errorCode;
this.errorClass = errorClass;
this.host = host;
}

/**
* Creates a new QueryInterruptedException wrapping an underlying exception. The errorMessage and errorClass
* of this exception will be based on the highest non-QueryInterruptedException in the causality chain.
*
* @param cause wrapped exception
*/
public QueryInterruptedException(Throwable cause)
{
this(cause, null);
this(cause, getHostFromThrowable(cause));
}

public QueryInterruptedException(Throwable e, String host)
public QueryInterruptedException(Throwable cause, String host)
{
super(e);
super(cause == null ? null : cause.getMessage(), cause);
this.errorCode = getErrorCodeFromThrowable(cause);
this.errorClass = getErrorClassFromThrowable(cause);
this.host = host;
causeMessage = e.getMessage();
}

@JsonProperty("error")
public String getErrorCode()
{
return errorCode;
}

@JsonProperty("errorMessage")
@Override
public String getMessage()
{
if (this.getCause() == null) {
return super.getMessage();
} else if (this.getCause() instanceof QueryInterruptedException) {
return getCause().getMessage();
} else if (this.getCause() instanceof InterruptedException) {
return super.getMessage();
}

@JsonProperty
public String getErrorClass()
{
return errorClass;
}

@JsonProperty
public String getHost()
{
return host;
}

private static String getErrorCodeFromThrowable(Throwable e)
{
if (e instanceof QueryInterruptedException) {
return ((QueryInterruptedException) e).getErrorCode();
} else if (e instanceof InterruptedException) {
return QUERY_INTERRUPTED;
} else if (this.getCause() instanceof CancellationException) {
} else if (e instanceof CancellationException) {
return QUERY_CANCELLED;
} else if (this.getCause() instanceof TimeoutException) {
} else if (e instanceof TimeoutException) {
return QUERY_TIMEOUT;
} else {
return UNKNOWN_EXCEPTION;
}
}

@JsonProperty("causeMessage")
public String getCauseMessage()
private static String getErrorClassFromThrowable(Throwable e)
{
return causeMessage;
}

@JsonProperty("host")
public String getHost()
{
return host;
if (e instanceof QueryInterruptedException) {
return ((QueryInterruptedException) e).getErrorClass();
} else if (e != null) {
return e.getClass().getName();
} else {
return null;
}
}

public boolean isNotKnown()
private static String getHostFromThrowable(Throwable e)
{
return !listKnownException.contains(getMessage());
if (e instanceof QueryInterruptedException) {
return ((QueryInterruptedException) e).getHost();
} else {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ public void run()
}
catch (ExecutionException e) {
Assert.assertTrue(e.getCause() instanceof QueryInterruptedException);
Assert.assertEquals("Query timeout", e.getCause().getMessage());
Assert.assertEquals("Query timeout", ((QueryInterruptedException) e.getCause()).getErrorCode());
cause = (QueryInterruptedException) e.getCause();
}
queriesInterrupted.await();
Expand Down
Loading