void (@NotNull BiFunction<? super ObjectTree, ? super Throwable, ? extends @NotNull List<Disposable>> removeFromTreeAction) { Throwable trace = null; boolean needTrace = Disposer.isDebugMode() && (trace = ourTopmostDisposeTrace.get()) == null; if (needTrace) { trace = ThrowableInterner.intern(new Throwable()); ourTopmostDisposeTrace.set(trace); } // first, atomically remove disposables from the tree to avoid "register during dispose" race conditions List<Disposable> disposables; synchronized (getTreeLock()) { disposables = removeFromTreeAction.apply(this, trace); } // second, call "beforeTreeDispose" in pre-order (some clients are hardcoded to see parents-then-children order in "beforeTreeDispose") List<Throwable> exceptions = null; for (int i = disposables.size() - 1; i >= 0; i--) { Disposable disposable = disposables.get(i); if (disposable instanceof Disposable.Parent) { try { ((Disposable.Parent)disposable).beforeTreeDispose(); } catch (Throwable t) { if (exceptions == null) exceptions = new SmartList<>(); exceptions.add(t); } } } // third, dispose in post-order (bottom-up) for (Disposable disposable: disposables) { try { //noinspection SSBasedInspection disposable.dispose(); } catch (Throwable e) { if (exceptions == null) exceptions = new SmartList<>(); exceptions.add(e); } } if (needTrace) { ourTopmostDisposeTrace.remove(); } if (exceptions != null) { handleExceptions(exceptions); } }