private void runWithTrace(@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);
    }
  }
private void runWithTrace(@NotNull BiFunction<? super ObjectTree, ? super Throwable, ? extends @NotNull List<Disposable>> removeFromTreeAction)