All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.drill.common.exceptions.UserException Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.drill.common.exceptions;

import static java.lang.Thread.sleep;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.lang.management.ManagementFactory;

import org.apache.drill.exec.proto.CoordinationProtos;
import org.apache.drill.exec.proto.CoordinationProtos.DrillbitEndpoint;
import org.apache.drill.exec.proto.UserBitShared.DrillPBError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * Base class for all user exception. The goal is to separate out common error conditions where we can give users
 * useful feedback.
 * 

Throwing a user exception will guarantee it's message will be displayed to the user, along with any context * information added to the exception at various levels while being sent to the client. *

A specific class of user exceptions are system exception. They represent system level errors that don't display * any specific error message to the user apart from "A system error has occurred" along with information to retrieve * the details of the exception from the logs. *

Although system exception should only display a generic message to the user, for now they will display the root * error message, until all user errors are properly sent from the server side. *

Any thrown exception that is not wrapped inside a user exception will automatically be converted to a system * exception before being sent to the client. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType */ public class UserException extends DrillRuntimeException { private static final long serialVersionUID = -6720929331624621840L; private static final Logger logger = LoggerFactory.getLogger(UserException.class); public static final String MEMORY_ERROR_MSG = "One or more nodes ran out of memory while executing the query."; /** * Creates a RESOURCE error with a prebuilt message for out of memory exceptions * * @param cause exception that will be wrapped inside a memory error * @return resource error builder */ public static Builder memoryError(final Throwable cause) { return UserException.resourceError(cause) .message(MEMORY_ERROR_MSG).addContext(cause.getMessage()); } public static Builder memoryError(final String format, final Object... args) { final Builder builder = UserException.resourceError(); builder.message(MEMORY_ERROR_MSG); if (!format.isEmpty()) { builder.addContext(String.format(format, args)); } return builder; } /** * Creates a RESOURCE error with a prebuilt message for out of memory exceptions * * @return resource error builder */ public static Builder memoryError() { return memoryError(""); } /** * Wraps the passed exception inside a system error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#SYSTEM * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder systemError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.SYSTEM, cause); } /** * Creates a new user exception builder. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#CONNECTION * @return user exception builder */ public static Builder connectionError() { return connectionError(null); } /** * Wraps the passed exception inside a connection error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#CONNECTION * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder connectionError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.CONNECTION, cause); } /** * Creates a new user exception builder. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#DATA_READ * @return user exception builder */ public static Builder dataReadError() { return dataReadError(null); } /** * Wraps the passed exception inside a data read error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#DATA_READ * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder dataReadError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.DATA_READ, cause); } /** * Creates a new user exception builder . * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#DATA_WRITE * @return user exception builder */ public static Builder dataWriteError() { return dataWriteError(null); } /** * Wraps the passed exception inside a data write error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#DATA_WRITE * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder dataWriteError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.DATA_WRITE, cause); } /** * Creates a new user exception builder . * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#FUNCTION * @return user exception builder */ public static Builder functionError() { return functionError(null); } /** * Wraps the passed exception inside a function error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#FUNCTION * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder functionError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.FUNCTION, cause); } /** * Creates a new user exception builder . * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#PARSE * @return user exception builder */ public static Builder parseError() { return parseError(null); } /** * Wraps the passed exception inside a parse error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#PARSE * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder parseError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.PARSE, cause); } /** * Creates a new user exception builder . * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#VALIDATION * @return user exception builder */ public static Builder validationError() { return validationError(null); } /** * wraps the passed exception inside a validation error. *

the cause message will be used unless {@link Builder#message(String, Object...)} is called. *

if the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#VALIDATION * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder validationError(Throwable cause) { return new Builder(DrillPBError.ErrorType.VALIDATION, cause); } /** * creates a new user exception builder . * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#PERMISSION * @return user exception builder */ public static Builder permissionError() { return permissionError(null); } /** * Wraps the passed exception inside a permission error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#PERMISSION * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder permissionError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.PERMISSION, cause); } /** * Creates a new user exception builder . * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#PLAN * @return user exception builder */ public static Builder planError() { return planError(null); } /** * Wraps the passed exception inside a plan error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#PLAN * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder planError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.PLAN, cause); } /** * Wraps the passed exception inside a plugin error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by * {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception * as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#PLUGIN * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user * exception it will be returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder pluginError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.PLUGIN, cause); } /** * Creates a new user exception builder . * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#RESOURCE * @return user exception builder */ public static Builder resourceError() { return resourceError(null); } /** * Wraps the passed exception inside a resource error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#RESOURCE * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder resourceError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.RESOURCE, cause); } /** * Creates a new user exception builder . * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#UNSUPPORTED_OPERATION * @return user exception builder */ public static Builder unsupportedError() { return unsupportedError(null); } /** * Wraps the passed exception inside a unsupported error. *

The cause message will be used unless {@link Builder#message(String, Object...)} is called. *

If the wrapped exception is, or wraps, a user exception it will be returned by {@link Builder#build(Logger)} * instead of creating a new exception. Any added context will be added to the user exception as well. * * @see org.apache.drill.exec.proto.UserBitShared.DrillPBError.ErrorType#UNSUPPORTED_OPERATION * * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder unsupportedError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.UNSUPPORTED_OPERATION, cause); } /** * Report an unsupported schema change. * * @param cause the SchemaChangeException. (Not typed because that * class is not visible to this package.) * @return user exception builder. */ public static Builder schemaChangeError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.UNSUPPORTED_OPERATION, cause) .addContext("Unsupported schema change"); } public static Builder schemaChangeError() { return schemaChangeError(null); } /** * Wraps an error that arises from execution due to issues in the query, in * the environment and so on -- anything other than "this should never occur" * type checks. * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder executionError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.EXECUTION_ERROR, cause); } /** * Indicates an internal validation failed or similar unexpected error. Indicates * the problem is likely within Drill itself rather than due to the environment, * query, etc. * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder internalError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.INTERNAL_ERROR, cause); } public static Builder internalError() { return new Builder(DrillPBError.ErrorType.INTERNAL_ERROR, null); } /** * Indicates an unspecified error: code caught the exception, but does not have * visibility into the cause well enough to pick one of the more specific * error types. In practice, using this exception indicates that error handling * should be moved closer to the source of the exception so we can provide the * user with a better explanation than "something went wrong." * @param cause exception we want the user exception to wrap. If cause is, or wraps, a user exception it will be * returned by the builder instead of creating a new user exception * @return user exception builder */ public static Builder unspecifiedError(final Throwable cause) { return new Builder(DrillPBError.ErrorType.UNSPECIFIED_ERROR, cause); } /** * Builder class for DrillUserException. You can wrap an existing exception, in this case it will first check if * this exception is, or wraps, a DrillUserException. If it does then the builder will use the user exception as it is * (it will ignore the message passed to the constructor) and will add any additional context information to the * exception's context */ public static class Builder { private final Throwable cause; private final UserException uex; private final UserExceptionContext context; private DrillPBError.ErrorType errorType; private String message; /** * Wraps an existing exception inside a user exception. * * @param errorType user exception type that should be created if the passed exception isn't, * or doesn't wrap a user exception * @param cause exception to wrap inside a user exception. Can be null */ private Builder(final DrillPBError.ErrorType errorType, final Throwable cause) { this.cause = cause; //TODO handle the improbable case where cause is a SYSTEM exception ? uex = ErrorHelper.findWrappedUserException(cause); if (uex != null) { this.errorType = null; this.context = uex.context; } else { // we will create a new user exception this.errorType = errorType; this.context = new UserExceptionContext(); this.message = cause != null ? cause.getMessage() : null; } } private Builder(UserException uex) { this.uex = uex; cause = uex.getCause(); errorType = uex.errorType; context = uex.context; message = uex.getOriginalMessage(); } /** * Sets the error type. This method is normally not needed because of * the per-error-type static initialiser methods above but it may be * used in conjunction with {@link rebuild()} to clone an existing * UserException modified to use a different error type. * @param errorType the error type to set. * @return this builder. */ public Builder errorType(DrillPBError.ErrorType errorType) { this.errorType = errorType; return this; } /** * sets or replaces the error message. *

This will be ignored if this builder is wrapping a user exception * * @see String#format(String, Object...) * * @param format format string * @param args Arguments referenced by the format specifiers in the format string * @return this builder */ public Builder message(final String format, final Object... args) { // we can't replace the message of a user exception if (uex == null && format != null) { if (args.length == 0) { message = format; } else { message = String.format(format, args); } } return this; } /** * add DrillbitEndpoint identity to the context. *

if the context already has a drillbitEndpoint identity, the new identity will be ignored * * @param endpoint drillbit endpoint identity */ public Builder addIdentity(final CoordinationProtos.DrillbitEndpoint endpoint) { context.add(endpoint); return this; } /** * add a string line to the bottom of the context * @param value string line * @return this builder */ public Builder addContext(final String value) { context.add(value); return this; } /** * add a string line to the bottom of the context * @param value string line * @return this builder */ public Builder addContext(final String value, Object... args) { context.add(String.format(value, args)); return this; } /** * add a string value to the bottom of the context * * @param name context name * @param value context value * @return this builder */ public Builder addContext(final String name, final String value) { context.add(name, value); return this; } /** * add a long value to the bottom of the context * * @param name context name * @param value context value * @return this builder */ public Builder addContext(final String name, final long value) { context.add(name, value); return this; } /** * add a double value to the bottom of the context * * @param name context name * @param value context value * @return this builder */ public Builder addContext(final String name, final double value) { context.add(name, value); return this; } public Builder addContext(CustomErrorContext context) { if (context != null) { context.addContext(this); } return this; } /** * pushes a string value to the top of the context * * @param value context value * @return this builder */ public Builder pushContext(final String value) { context.push(value); return this; } /** * pushes a string value to the top of the context * * @param name context name * @param value context value * @return this builder */ public Builder pushContext(final String name, final String value) { context.push(name, value); return this; } /** * pushes a long value to the top of the context * * @param name context name * @param value context value * @return this builder */ public Builder pushContext(final String name, final long value) { context.push(name, value); return this; } /** * pushes a double value to the top of the context * * @param name context name * @param value context value * @return this builder */ public Builder pushContext(final String name, final double value) { context.push(name, value); return this; } private static final File SPIN_FILE = new File("/tmp/drill/spin"); /** * builds a user exception or returns the wrapped one. If the error is a system error, the error message is logged * to the given {@link Logger}. * * @param logger the logger to write to * @return user exception */ public UserException build(final Logger logger) { // To allow for debugging: // // A spinner code to make the execution stop here while the file '/tmp/drill/spin' exists // Can be used to attach a debugger, use jstack, etc // (do "clush -a touch /tmp/drill/spin" to turn this on across all the cluster nodes, and to // release the spinning threads do "clush -a rm /tmp/drill/spin") // The processID of the spinning thread (along with the error message) should then be found // in a file like /tmp/drill/spin4148663301172491613.tmp if (SPIN_FILE.exists()) { final File tmpDir = new File("/tmp/drill"); File outErr = null; try { outErr = File.createTempFile("spin", ".tmp", tmpDir); final BufferedWriter bw = new BufferedWriter(new FileWriter(outErr)); bw.write("Spinning process: " + ManagementFactory.getRuntimeMXBean().getName() /* After upgrading to JDK 9 - replace with: ProcessHandle.current().getPid() */); bw.write("\nError cause: " + (errorType == DrillPBError.ErrorType.SYSTEM ? ("SYSTEM ERROR: " + ErrorHelper.getRootMessage(cause)) : message)); bw.close(); } catch (final Exception ex) { logger.warn("Failed creating a spinner tmp message file: {}", ex); } while (SPIN_FILE.exists()) { try { sleep(1_000); } catch (final Exception ex) { /* ignore interruptions */ } } try { outErr.delete(); } catch (final Exception ex) { } // cleanup - remove err msg file } if (uex != null) { return uex; } final boolean isSystemError = errorType == DrillPBError.ErrorType.SYSTEM; // make sure system errors use the root error message and display the root cause class name if (isSystemError) { message = ErrorHelper.getRootMessage(cause); } final UserException newException = new UserException(this); // since we just created a new exception, we should log it for later reference. If this is a system error, this is // an issue that the Drill admin should pay attention to and we should log as ERROR. However, if this is a user // mistake or data read issue, the system admin should not be concerned about these and thus we'll log this // as an INFO message. if (isSystemError) { logger.error(newException.getMessage(), newException); } else { final StringBuilder buf = new StringBuilder(); buf.append("User Error Occurred"); if (message != null) { buf.append(": ").append(message); } if (cause != null) { buf.append(" (").append(cause.getMessage()).append(")"); } logger.info(buf.toString(), newException); } return newException; } /** * Builds a user exception or returns the wrapped one. * * @return user exception * @deprecated Use {@link #build(Logger)} instead. If the error is a system error, the error message is logged to * this {@link UserException#logger}. */ @Deprecated public UserException build() { return build(logger); } } private final DrillPBError.ErrorType errorType; private final UserExceptionContext context; protected UserException(final DrillPBError.ErrorType errorType, final String message, final Throwable cause) { super(message, cause); this.errorType = errorType; this.context = new UserExceptionContext(); } private UserException(final Builder builder) { super(builder.message, builder.cause); this.errorType = builder.errorType; this.context = builder.context; } public Builder rebuild() { return new Builder(this); } /** * generates the message that will be displayed to the client without the stack trace. * * @return non verbose error message */ @Override public String getMessage() { return generateMessage(true); } public String getMessage(boolean includeErrorIdAndIdentity) { return generateMessage(includeErrorIdAndIdentity); } /** * * @return the error message that was passed to the builder */ public String getOriginalMessage() { return super.getMessage(); } /** * generates the message that will be displayed to the client. The message also contains the stack trace. * * @return verbose error message */ public String getVerboseMessage() { return getVerboseMessage(true); } public String getVerboseMessage(boolean includeErrorIdAndIdentity) { return generateMessage(includeErrorIdAndIdentity) + "\n\n" + ErrorHelper.buildCausesMessage(getCause()); } /** * returns or creates a DrillPBError object corresponding to this user exception. * * @param verbose should the error object contain the verbose error message ? * @return protobuf error object */ public DrillPBError getOrCreatePBError(final boolean verbose) { final String message = verbose ? getVerboseMessage() : getMessage(); final DrillPBError.Builder builder = DrillPBError.newBuilder(); builder.setErrorType(errorType); builder.setErrorId(context.getErrorId()); if (context.getEndpoint() != null) { builder.setEndpoint(context.getEndpoint()); } builder.setMessage(message); if (getCause() != null) { // some unit tests use this information to make sure a specific exception was thrown in the server builder.setException(ErrorHelper.getWrapper(getCause())); } else { // not a wrapper exception builder.setException(ErrorHelper.getWrapper(this)); } return builder.build(); } public String getErrorId() { return context.getErrorId(); } public DrillPBError.ErrorType getErrorType() { return errorType; } public String getErrorLocation() { final DrillbitEndpoint ep = context.getEndpoint(); if (ep != null) { return ep.getAddress() + ":" + ep.getUserPort(); } else { return null; } } /** * Generates a user error message that has the following structure: * ERROR TYPE: ERROR_MESSAGE * CONTEXT * [ERROR_ID on DRILLBIT_IP:DRILLBIT_USER_PORT] * * @return generated user error message */ private String generateMessage(boolean includeErrorIdAndIdentity) { final boolean seeLogsMessage = errorType == DrillPBError.ErrorType.INTERNAL_ERROR || errorType == DrillPBError.ErrorType.SYSTEM; return errorType + " ERROR: " + super.getMessage() + "\n\n" + context.generateContextMessage(includeErrorIdAndIdentity, seeLogsMessage); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy