diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java b/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java index cd69371f3f..a8d2291357 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java @@ -24,154 +24,210 @@ public class SpyImpl extends AbstractSpy { private static final Logger logger = LoggerFactory.getLogger(SpyImpl.class); + /** + * Thread-local reentrancy guard to prevent recursive advice dispatch. + * When ObjectView renders an object and calls toString(), that toString() + * call must not re-trigger advice listeners on the same thread. + */ + private static final ThreadLocal DISPATCHING = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return Boolean.FALSE; + } + }; + @Override public void atEnter(Class clazz, String methodInfo, Object target, Object[] args) { - ClassLoader classLoader = clazz.getClassLoader(); - - String[] info = StringUtils.splitMethodInfo(methodInfo); - String methodName = info[0]; - String methodDesc = info[1]; - // TODO listener 只用查一次,放到 thread local里保存起来就可以了! - List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(), - methodName, methodDesc); - if (listeners != null) { - for (AdviceListener adviceListener : listeners) { - try { - if (skipAdviceListener(adviceListener)) { - continue; + if (DISPATCHING.get()) { + logger.debug("Skipping re-entrant atEnter advice dispatch for class: {}, methodInfo: {}", clazz.getName(), methodInfo); + return; + } + DISPATCHING.set(Boolean.TRUE); + try { + ClassLoader classLoader = clazz.getClassLoader(); + String[] info = StringUtils.splitMethodInfo(methodInfo); + String methodName = info[0]; + String methodDesc = info[1]; + // TODO listener 只用查一次,放到 thread local里保存起来就可以了! + List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(), + methodName, methodDesc); + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + adviceListener.before(clazz, methodName, methodDesc, target, args); + } catch (Throwable e) { + logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e); } - adviceListener.before(clazz, methodName, methodDesc, target, args); - } catch (Throwable e) { - logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e); } } + } finally { + DISPATCHING.set(Boolean.FALSE); } - } @Override public void atExit(Class clazz, String methodInfo, Object target, Object[] args, Object returnObject) { - ClassLoader classLoader = clazz.getClassLoader(); - - String[] info = StringUtils.splitMethodInfo(methodInfo); - String methodName = info[0]; - String methodDesc = info[1]; - - List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(), - methodName, methodDesc); - if (listeners != null) { - for (AdviceListener adviceListener : listeners) { - try { - if (skipAdviceListener(adviceListener)) { - continue; + if (DISPATCHING.get()) { + logger.debug("Skipping re-entrant atExit advice dispatch for class: {}, methodInfo: {}", clazz.getName(), methodInfo); + return; + } + DISPATCHING.set(Boolean.TRUE); + try { + ClassLoader classLoader = clazz.getClassLoader(); + String[] info = StringUtils.splitMethodInfo(methodInfo); + String methodName = info[0]; + String methodDesc = info[1]; + List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(), + methodName, methodDesc); + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + adviceListener.afterReturning(clazz, methodName, methodDesc, target, args, returnObject); + } catch (Throwable e) { + logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e); } - adviceListener.afterReturning(clazz, methodName, methodDesc, target, args, returnObject); - } catch (Throwable e) { - logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e); } } + } finally { + DISPATCHING.set(Boolean.FALSE); } } @Override public void atExceptionExit(Class clazz, String methodInfo, Object target, Object[] args, Throwable throwable) { - ClassLoader classLoader = clazz.getClassLoader(); - - String[] info = StringUtils.splitMethodInfo(methodInfo); - String methodName = info[0]; - String methodDesc = info[1]; - - List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(), - methodName, methodDesc); - if (listeners != null) { - for (AdviceListener adviceListener : listeners) { - try { - if (skipAdviceListener(adviceListener)) { - continue; + if (DISPATCHING.get()) { + logger.debug("Skipping re-entrant atExceptionExit advice dispatch for class: {}, methodInfo: {}", clazz.getName(), methodInfo); + return; + } + DISPATCHING.set(Boolean.TRUE); + try { + ClassLoader classLoader = clazz.getClassLoader(); + String[] info = StringUtils.splitMethodInfo(methodInfo); + String methodName = info[0]; + String methodDesc = info[1]; + List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(), + methodName, methodDesc); + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + adviceListener.afterThrowing(clazz, methodName, methodDesc, target, args, throwable); + } catch (Throwable e) { + logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e); } - adviceListener.afterThrowing(clazz, methodName, methodDesc, target, args, throwable); - } catch (Throwable e) { - logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e); } } + } finally { + DISPATCHING.set(Boolean.FALSE); } } @Override public void atBeforeInvoke(Class clazz, String invokeInfo, Object target) { - ClassLoader classLoader = clazz.getClassLoader(); - String[] info = StringUtils.splitInvokeInfo(invokeInfo); - String owner = info[0]; - String methodName = info[1]; - String methodDesc = info[2]; - - List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(), - owner, methodName, methodDesc); - - if (listeners != null) { - for (AdviceListener adviceListener : listeners) { - try { - if (skipAdviceListener(adviceListener)) { - continue; + if (DISPATCHING.get()) { + return; + } + DISPATCHING.set(Boolean.TRUE); + try { + ClassLoader classLoader = clazz.getClassLoader(); + String[] info = StringUtils.splitInvokeInfo(invokeInfo); + String owner = info[0]; + String methodName = info[1]; + String methodDesc = info[2]; + + List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(), + owner, methodName, methodDesc); + + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + final InvokeTraceable listener = (InvokeTraceable) adviceListener; + listener.invokeBeforeTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3])); + } catch (Throwable e) { + logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e); } - final InvokeTraceable listener = (InvokeTraceable) adviceListener; - listener.invokeBeforeTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3])); - } catch (Throwable e) { - logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e); } } + } finally { + DISPATCHING.set(Boolean.FALSE); } } @Override public void atAfterInvoke(Class clazz, String invokeInfo, Object target) { - ClassLoader classLoader = clazz.getClassLoader(); - String[] info = StringUtils.splitInvokeInfo(invokeInfo); - String owner = info[0]; - String methodName = info[1]; - String methodDesc = info[2]; - List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(), - owner, methodName, methodDesc); - - if (listeners != null) { - for (AdviceListener adviceListener : listeners) { - try { - if (skipAdviceListener(adviceListener)) { - continue; + if (DISPATCHING.get()) { + return; + } + DISPATCHING.set(Boolean.TRUE); + try { + ClassLoader classLoader = clazz.getClassLoader(); + String[] info = StringUtils.splitInvokeInfo(invokeInfo); + String owner = info[0]; + String methodName = info[1]; + String methodDesc = info[2]; + List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(), + owner, methodName, methodDesc); + + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + final InvokeTraceable listener = (InvokeTraceable) adviceListener; + listener.invokeAfterTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3])); + } catch (Throwable e) { + logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e); } - final InvokeTraceable listener = (InvokeTraceable) adviceListener; - listener.invokeAfterTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3])); - } catch (Throwable e) { - logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e); } } + } finally { + DISPATCHING.set(Boolean.FALSE); } - } @Override public void atInvokeException(Class clazz, String invokeInfo, Object target, Throwable throwable) { - ClassLoader classLoader = clazz.getClassLoader(); - String[] info = StringUtils.splitInvokeInfo(invokeInfo); - String owner = info[0]; - String methodName = info[1]; - String methodDesc = info[2]; - - List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(), - owner, methodName, methodDesc); - - if (listeners != null) { - for (AdviceListener adviceListener : listeners) { - try { - if (skipAdviceListener(adviceListener)) { - continue; + if (DISPATCHING.get()) { + return; + } + DISPATCHING.set(Boolean.TRUE); + try { + ClassLoader classLoader = clazz.getClassLoader(); + String[] info = StringUtils.splitInvokeInfo(invokeInfo); + String owner = info[0]; + String methodName = info[1]; + String methodDesc = info[2]; + + List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(), + owner, methodName, methodDesc); + + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + final InvokeTraceable listener = (InvokeTraceable) adviceListener; + listener.invokeThrowTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3])); + } catch (Throwable e) { + logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e); } - final InvokeTraceable listener = (InvokeTraceable) adviceListener; - listener.invokeThrowTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3])); - } catch (Throwable e) { - logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e); } } + } finally { + DISPATCHING.set(Boolean.FALSE); } }