/* * 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.flink.runtime.util; import org.apache.flink.util.ExceptionUtils; import org.apache.flink.util.InstantiationUtil; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.Set; /** * Utility class for dealing with user-defined Throwable types that are serialized (for * example during RPC/Actor communication), but cannot be resolved with the default * class loader. * * <p>This exception mimics the original exception with respect to message and stack trace, * and contains the original exception in serialized form. The original exception * can be re-obtained by supplying the appropriate class loader. */ public class SerializedThrowable extends Exception implements Serializable { private static final long serialVersionUID = 7284183123441947635L; /** The original exception in serialized form */ private final byte[] serializedException; /** Name of the original error class */ private final String originalErrorClassName; /** The original stack trace, to be printed */ private final String fullStringifiedStackTrace; /** The original exception, not transported via serialization, * because the class may not be part of the system class loader. * In addition, we make sure our cached references to not prevent * unloading the exception class. */ private transient WeakReference<Throwable> cachedException; /** * Create a new SerializedThrowable. * * @param exception The exception to serialize. */ public SerializedThrowable(Throwable exception) { this(exception, new HashSet<Throwable>()); } private SerializedThrowable(Throwable exception, Set<Throwable> alreadySeen) { super(getMessageOrError(exception)); if (!(exception instanceof SerializedThrowable)) { // serialize and memoize the original message byte[] serialized; try { serialized = InstantiationUtil.serializeObject(exception); } catch (Throwable t) { serialized = null; } this.serializedException = serialized; this.cachedException = new WeakReference<Throwable>(exception); // record the original exception's properties (name, stack prints) this.originalErrorClassName = exception.getClass().getName(); this.fullStringifiedStackTrace = ExceptionUtils.stringifyException(exception); // mimic the original exception's stack trace setStackTrace(exception.getStackTrace()); // mimic the original exception's cause if (exception.getCause() == null) { initCause(null); } else { // exception causes may by cyclic, so we truncate the cycle when we find it if (alreadySeen.add(exception)) { // we are not in a cycle, yet initCause(new SerializedThrowable(exception.getCause(), alreadySeen)); } } } else { // copy from that serialized throwable SerializedThrowable other = (SerializedThrowable) exception; this.serializedException = other.serializedException; this.originalErrorClassName = other.originalErrorClassName; this.fullStringifiedStackTrace = other.fullStringifiedStackTrace; this.cachedException = other.cachedException; this.setStackTrace(other.getStackTrace()); this.initCause(other.getCause()); } } public Throwable deserializeError(ClassLoader classloader) { if (serializedException == null) { // failed to serialize the original exception // return this SerializedThrowable as a stand in return this; } Throwable cached = cachedException == null ? null : cachedException.get(); if (cached == null) { try { cached = InstantiationUtil.deserializeObject(serializedException, classloader); cachedException = new WeakReference<Throwable>(cached); } catch (Throwable t) { // something went wrong // return this SerializedThrowable as a stand in return this; } } return cached; } public String getOriginalErrorClassName() { return originalErrorClassName; } // ------------------------------------------------------------------------ // Override the behavior of Throwable // ------------------------------------------------------------------------ @Override public void printStackTrace(PrintStream s) { s.print(fullStringifiedStackTrace); s.flush(); } @Override public void printStackTrace(PrintWriter s) { s.print(fullStringifiedStackTrace); s.flush(); } @Override public String toString() { String message = getLocalizedMessage(); return (message != null) ? (originalErrorClassName + ": " + message) : originalErrorClassName; } // ------------------------------------------------------------------------ // Static utilities // ------------------------------------------------------------------------ public static Throwable get(Throwable serThrowable, ClassLoader loader) { if (serThrowable instanceof SerializedThrowable) { return ((SerializedThrowable)serThrowable).deserializeError(loader); } else { return serThrowable; } } private static String getMessageOrError(Throwable error) { try { return error.getMessage(); } catch (Throwable t) { return "(failed to get message)"; } } }