View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.errorhandling;
19  
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.apache.hadoop.classification.InterfaceAudience;
25  import org.apache.hadoop.classification.InterfaceStability;
26  import org.apache.hadoop.hbase.protobuf.generated.ErrorHandlingProtos.ForeignExceptionMessage;
27  import org.apache.hadoop.hbase.protobuf.generated.ErrorHandlingProtos.GenericExceptionMessage;
28  import org.apache.hadoop.hbase.protobuf.generated.ErrorHandlingProtos.StackTraceElementMessage;
29  
30  import com.google.protobuf.InvalidProtocolBufferException;
31  
32  /**
33   * A ForeignException is an exception from another thread or process.
34   * <p>
35   * ForeignExceptions are sent to 'remote' peers to signal an abort in the face of failures.
36   * When serialized for transmission we encode using Protobufs to ensure version compatibility.
37   * <p>
38   * Foreign exceptions contain a Throwable as its cause.  This can be a "regular" exception
39   * generated locally or a ProxyThrowable that is a representation of the original exception
40   * created on original 'remote' source.  These ProxyThrowables have their their stacks traces and
41   * messages overridden to reflect the original 'remote' exception.  The only way these
42   * ProxyThrowables are generated are by this class's {@link #deserialize(byte[])} method.
43   */
44  @InterfaceAudience.Public
45  @InterfaceStability.Evolving
46  @SuppressWarnings("serial")
47  public class ForeignException extends IOException {
48  
49    /**
50     * Name of the throwable's source such as a host or thread name.  Must be non-null.
51     */
52    private final String source;
53  
54    /**
55     * Create a new ForeignException that can be serialized.  It is assumed that this came form a
56     * local source.
57     * @param source
58     * @param cause
59     */
60    public ForeignException(String source, Throwable cause) {
61      super(cause);
62      assert source != null;
63      assert cause != null;
64      this.source = source;
65    }
66  
67    /**
68     * Create a new ForeignException that can be serialized.  It is assumed that this is locally
69     * generated.
70     * @param source
71     * @param msg
72     */
73    public ForeignException(String source, String msg) {
74      super(new IllegalArgumentException(msg));
75      this.source = source;
76    }
77  
78    public String getSource() {
79      return source;
80    }
81  
82    /**
83     * The cause of a ForeignException can be an exception that was generated on a local in process
84     * thread, or a thread from a 'remote' separate process.
85     *
86     * If the cause is a ProxyThrowable, we know it came from deserialization which usually means
87     * it came from not only another thread, but also from a remote thread.
88     *
89     * @return true if went through deserialization, false if locally generated
90     */
91    public boolean isRemote() {
92      return getCause() instanceof ProxyThrowable;
93    }
94  
95    @Override
96    public String toString() {
97      String className = getCause().getClass().getName()  ;
98      return className + " via " + getSource() + ":" + getLocalizedMessage();
99    }
100 
101   /**
102    * Convert a stack trace to list of {@link StackTraceElement}.
103    * @param trace the stack trace to convert to protobuf message
104    * @return <tt>null</tt> if the passed stack is <tt>null</tt>.
105    */
106   private static List<StackTraceElementMessage> toStackTraceElementMessages(
107       StackTraceElement[] trace) {
108     // if there is no stack trace, ignore it and just return the message
109     if (trace == null) return null;
110     // build the stack trace for the message
111     List<StackTraceElementMessage> pbTrace =
112         new ArrayList<StackTraceElementMessage>(trace.length);
113     for (StackTraceElement elem : trace) {
114       StackTraceElementMessage.Builder stackBuilder = StackTraceElementMessage.newBuilder();
115       stackBuilder.setDeclaringClass(elem.getClassName());
116       stackBuilder.setFileName(elem.getFileName());
117       stackBuilder.setLineNumber(elem.getLineNumber());
118       stackBuilder.setMethodName(elem.getMethodName());
119       pbTrace.add(stackBuilder.build());
120     }
121     return pbTrace;
122   }
123 
124   /**
125    * This is a Proxy Throwable that contains the information of the original remote exception
126    */
127   private static class ProxyThrowable extends Throwable {
128     ProxyThrowable(String msg, StackTraceElement[] trace) {
129       super(msg);
130       this.setStackTrace(trace);
131     }
132   }
133 
134   /**
135    * Converts a ForeignException to an array of bytes.
136    * @param source the name of the external exception source
137    * @param t the "local" external exception (local)
138    * @return protobuf serialized version of ForeignException
139    */
140   public static byte[] serialize(String source, Throwable t) {
141     GenericExceptionMessage.Builder gemBuilder = GenericExceptionMessage.newBuilder();
142     gemBuilder.setClassName(t.getClass().getName());
143     if (t.getMessage() != null) {
144       gemBuilder.setMessage(t.getMessage());
145     }
146     // set the stack trace, if there is one
147     List<StackTraceElementMessage> stack =
148         ForeignException.toStackTraceElementMessages(t.getStackTrace());
149     if (stack != null) {
150       gemBuilder.addAllTrace(stack);
151     }
152     GenericExceptionMessage payload = gemBuilder.build();
153     ForeignExceptionMessage.Builder exception = ForeignExceptionMessage.newBuilder();
154     exception.setGenericException(payload).setSource(source);
155     ForeignExceptionMessage eem = exception.build();
156     return eem.toByteArray();
157   }
158 
159   /**
160    * Takes a series of bytes and tries to generate an ForeignException instance for it.
161    * @param bytes
162    * @return the ForeignExcpetion instance
163    * @throws InvalidProtocolBufferException if there was deserialization problem this is thrown.
164    */
165   public static ForeignException deserialize(byte[] bytes) throws InvalidProtocolBufferException {
166     // figure out the data we need to pass
167     ForeignExceptionMessage eem = ForeignExceptionMessage.parseFrom(bytes);
168     GenericExceptionMessage gem = eem.getGenericException();
169     StackTraceElement [] trace = ForeignException.toStackTrace(gem.getTraceList());
170     ProxyThrowable dfe = new ProxyThrowable(gem.getMessage(), trace);
171     ForeignException e = new ForeignException(eem.getSource(), dfe);
172     return e;
173   }
174 
175   /**
176    * Unwind a serialized array of {@link StackTraceElementMessage}s to a
177    * {@link StackTraceElement}s.
178    * @param traceList list that was serialized
179    * @return the deserialized list or <tt>null</tt> if it couldn't be unwound (e.g. wasn't set on
180    *         the sender).
181    */
182   private static StackTraceElement[] toStackTrace(List<StackTraceElementMessage> traceList) {
183     if (traceList == null || traceList.size() == 0) {
184       return new StackTraceElement[0]; // empty array
185     }
186     StackTraceElement[] trace = new StackTraceElement[traceList.size()];
187     for (int i = 0; i < traceList.size(); i++) {
188       StackTraceElementMessage elem = traceList.get(i);
189       trace[i] = new StackTraceElement(
190           elem.getDeclaringClass(), elem.getMethodName(), elem.getFileName(), elem.getLineNumber());
191     }
192     return trace;
193   }
194 }