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}