001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.errorhandling;
019
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.List;
023
024import org.apache.yetus.audience.InterfaceAudience;
025import org.apache.hadoop.hbase.shaded.protobuf.generated.ErrorHandlingProtos.ForeignExceptionMessage;
026import org.apache.hadoop.hbase.shaded.protobuf.generated.ErrorHandlingProtos.GenericExceptionMessage;
027import org.apache.hadoop.hbase.shaded.protobuf.generated.ErrorHandlingProtos.StackTraceElementMessage;
028
029
030/**
031 * A ForeignException is an exception from another thread or process.
032 * <p>
033 * ForeignExceptions are sent to 'remote' peers to signal an abort in the face of failures.
034 * When serialized for transmission we encode using Protobufs to ensure version compatibility.
035 * <p>
036 * Foreign exceptions contain a Throwable as its cause.  This can be a "regular" exception
037 * generated locally or a ProxyThrowable that is a representation of the original exception
038 * created on original 'remote' source.  These ProxyThrowables have their their stacks traces and
039 * messages overridden to reflect the original 'remote' exception.  The only way these
040 * ProxyThrowables are generated are by this class's {@link #deserialize(byte[])} method.
041 */
042@InterfaceAudience.Public
043@SuppressWarnings("serial")
044public class ForeignException extends IOException {
045
046  /**
047   * Name of the throwable's source such as a host or thread name.  Must be non-null.
048   */
049  private final String source;
050
051  /**
052   * Create a new ForeignException that can be serialized.  It is assumed that this came form a
053   * local source.
054   * @param source
055   * @param cause
056   */
057  public ForeignException(String source, Throwable cause) {
058    super(cause);
059    assert source != null;
060    assert cause != null;
061    this.source = source;
062  }
063
064  /**
065   * Create a new ForeignException that can be serialized.  It is assumed that this is locally
066   * generated.
067   * @param source
068   * @param msg
069   */
070  public ForeignException(String source, String msg) {
071    super(new IllegalArgumentException(msg));
072    this.source = source;
073  }
074
075  public String getSource() {
076    return source;
077  }
078
079  /**
080   * The cause of a ForeignException can be an exception that was generated on a local in process
081   * thread, or a thread from a 'remote' separate process.
082   *
083   * If the cause is a ProxyThrowable, we know it came from deserialization which usually means
084   * it came from not only another thread, but also from a remote thread.
085   *
086   * @return true if went through deserialization, false if locally generated
087   */
088  public boolean isRemote() {
089    return getCause() instanceof ProxyThrowable;
090  }
091
092  @Override
093  public String toString() {
094    String className = getCause().getClass().getName()  ;
095    return className + " via " + getSource() + ":" + getLocalizedMessage();
096  }
097
098  /**
099   * Convert a stack trace to list of {@link StackTraceElement}.
100   * @param trace the stack trace to convert to protobuf message
101   * @return <tt>null</tt> if the passed stack is <tt>null</tt>.
102   */
103  private static List<StackTraceElementMessage> toStackTraceElementMessages(
104      StackTraceElement[] trace) {
105    // if there is no stack trace, ignore it and just return the message
106    if (trace == null) return null;
107    // build the stack trace for the message
108    List<StackTraceElementMessage> pbTrace = new ArrayList<>(trace.length);
109    for (StackTraceElement elem : trace) {
110      StackTraceElementMessage.Builder stackBuilder = StackTraceElementMessage.newBuilder();
111      stackBuilder.setDeclaringClass(elem.getClassName());
112      stackBuilder.setFileName(elem.getFileName());
113      stackBuilder.setLineNumber(elem.getLineNumber());
114      stackBuilder.setMethodName(elem.getMethodName());
115      pbTrace.add(stackBuilder.build());
116    }
117    return pbTrace;
118  }
119
120  /**
121   * This is a Proxy Throwable that contains the information of the original remote exception
122   */
123  private static class ProxyThrowable extends Throwable {
124    ProxyThrowable(String msg, StackTraceElement[] trace) {
125      super(msg);
126      this.setStackTrace(trace);
127    }
128  }
129
130  /**
131   * Converts a ForeignException to an array of bytes.
132   * @param source the name of the external exception source
133   * @param t the "local" external exception (local)
134   * @return protobuf serialized version of ForeignException
135   */
136  public static byte[] serialize(String source, Throwable t) {
137    GenericExceptionMessage.Builder gemBuilder = GenericExceptionMessage.newBuilder();
138    gemBuilder.setClassName(t.getClass().getName());
139    if (t.getMessage() != null) {
140      gemBuilder.setMessage(t.getMessage());
141    }
142    // set the stack trace, if there is one
143    List<StackTraceElementMessage> stack =
144        ForeignException.toStackTraceElementMessages(t.getStackTrace());
145    if (stack != null) {
146      gemBuilder.addAllTrace(stack);
147    }
148    GenericExceptionMessage payload = gemBuilder.build();
149    ForeignExceptionMessage.Builder exception = ForeignExceptionMessage.newBuilder();
150    exception.setGenericException(payload).setSource(source);
151    ForeignExceptionMessage eem = exception.build();
152    return eem.toByteArray();
153  }
154
155  /**
156   * Takes a series of bytes and tries to generate an ForeignException instance for it.
157   * @param bytes
158   * @return the ForeignExcpetion instance
159   * @throws InvalidProtocolBufferException if there was deserialization problem this is thrown.
160   * @throws org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException 
161   */
162  public static ForeignException deserialize(byte[] bytes)
163  throws IOException {
164    // figure out the data we need to pass
165    ForeignExceptionMessage eem = ForeignExceptionMessage.parseFrom(bytes);
166    GenericExceptionMessage gem = eem.getGenericException();
167    StackTraceElement [] trace = ForeignException.toStackTrace(gem.getTraceList());
168    ProxyThrowable dfe = new ProxyThrowable(gem.getMessage(), trace);
169    ForeignException e = new ForeignException(eem.getSource(), dfe);
170    return e;
171  }
172
173  /**
174   * Unwind a serialized array of {@link StackTraceElementMessage}s to a
175   * {@link StackTraceElement}s.
176   * @param traceList list that was serialized
177   * @return the deserialized list or <tt>null</tt> if it couldn't be unwound (e.g. wasn't set on
178   *         the sender).
179   */
180  private static StackTraceElement[] toStackTrace(List<StackTraceElementMessage> traceList) {
181    if (traceList == null || traceList.isEmpty()) {
182      return new StackTraceElement[0]; // empty array
183    }
184    StackTraceElement[] trace = new StackTraceElement[traceList.size()];
185    for (int i = 0; i < traceList.size(); i++) {
186      StackTraceElementMessage elem = traceList.get(i);
187      trace[i] = new StackTraceElement(
188          elem.getDeclaringClass(), elem.getMethodName(), elem.getFileName(), elem.getLineNumber());
189    }
190    return trace;
191  }
192}