E startInternal(boolean internalEvaluate, @NotNull ThreadReferenceProxyImpl invokeThread)
      throws EvaluateException, ClassNotLoadedException {
      DebuggerManagerThreadImpl.assertIsManagerThread();
      SuspendContextImpl suspendContext = myEvaluationContext.getSuspendContext();
      if (!suspendContext.myInProgress) {
        logError("You can invoke methods only inside commands invoked for SuspendContext. " +
                 "myEvaluationContext = " + myEvaluationContext + ", invokeThread = " + invokeThread);
      }

      if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD && suspendContext.getEventThread() != invokeThread) {
        logError("Event thread context is used to evaluate on another thread, context = " + suspendContext + ", invokeThread = " + invokeThread);
      }

      if (invokeThread.isEvaluating()) {
        throw EvaluateExceptionUtil.NESTED_EVALUATION_ERROR;
      }

      if (!suspendContext.suspends(invokeThread)) {
        throw EvaluateExceptionUtil.THREAD_WAS_RESUMED;
      }

      Set<SuspendContextImpl> suspendingContexts = SuspendManagerUtil.getSuspendingContexts(getSuspendManager(), invokeThread);

      myEvaluationDispatcher.getMulticaster().evaluationStarted(suspendContext);
      beforeMethodInvocation(suspendContext, myMethod, internalEvaluate);

      Object resumeData = null;
      try {
        for (SuspendContextImpl suspendingContext : suspendingContexts) {
          if (suspendingContext == suspendContext) {
            continue;
          }
          if (suspendingContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD) {
            // Event thread contexts may come from breakpoint, class prepare and so on.
            // But it may happen before (and already processed) or during evaluation (and will be processed).
            // It should not be ok to have several standing event thread contexts for the same thread.
            logError("Evaluating on " + suspendContext + ", but found existed event thread context " + suspendingContext);
          }
          if (LOG.isDebugEnabled()) {
            LOG.debug("Resuming " + invokeThread + " that is paused by " + suspendingContext);
          }
          getSuspendManager().resumeThread(suspendingContext, invokeThread);
        }

        if (!DebuggerUtils.isNewThreadSuspendStateTracking()) {
          resumeData = SuspendManagerUtil.prepareForResume(suspendContext);
        }
        myEvaluationContext.setThreadForEvaluation(invokeThread);

        invokeThread.getVirtualMachineProxy().clearCaches();

        return invokeMethodAndFork(suspendContext, invokeThread);
      }
      catch (IncompatibleThreadStateException e) {
        if (invokeThread == suspendContext.getEventThread()) {
          throw EvaluateExceptionUtil.createEvaluateException(e);
        }
        suspendContext.myNotExecutableThreads.add(invokeThread);
        String m = "Evaluation failed on non-primary thread '%s'. Will be a retry on primary suspended thread '%s'"
          .formatted(invokeThread, suspendContext.getEventThread());
        throw new RetryEvaluationException(m, e);
      }
      catch (InvocationException | InternalException | UnsupportedOperationException | ObjectCollectedException |
             InvalidTypeException e) {
        throw EvaluateExceptionUtil.createEvaluateException(e);
      }
      finally {
        if (LOG.isDebugEnabled()) {
          LOG.debug("Evaluation finished in " + suspendContext);
        }
        myEvaluationContext.setThreadForEvaluation(null);
        if (DebuggerUtils.isNewThreadSuspendStateTracking() && !mySuspendManager.myExplicitlyResumedThreads.contains(invokeThread)) {
          for (SuspendContextImpl anotherContext : mySuspendManager.getEventContexts()) {
            if (anotherContext != suspendContext && !anotherContext.suspends(invokeThread)) {
              boolean shouldSuspendThread = false;
              if (anotherContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD && anotherContext.getEventThread() == invokeThread) {
                // Event thread contexts may come from breakpoint, class prepare and so on.
                // But it may happen during evaluation and should be already processed.
                logError("Another event thread context after evaluation on " + invokeThread + " (" + suspendContext + ") : " + anotherContext);
                shouldSuspendThread = true;
              }
              if (anotherContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL) {
                if (anotherContext.myResumedThreads == null || !anotherContext.myResumedThreads.contains(invokeThread)) {
                  logError("Suspend all context claims not suspending " + invokeThread + " but its resumed threads have no it: " + anotherContext.myResumedThreads);
                }
                shouldSuspendThread = true;
              }
              if (shouldSuspendThread) {
                mySuspendManager.suspendThread(anotherContext, invokeThread);
              }
            }
          }
        }
        else {
          if (resumeData != null) {
            SuspendManagerUtil.restoreAfterResume(suspendContext, resumeData);
          }
          for (SuspendContextImpl suspendingContext : mySuspendManager.getEventContexts()) {
            if (suspendingContexts.contains(suspendingContext) &&
                suspendingContext != suspendContext &&
                !suspendingContext.isEvaluating() &&
                !suspendingContext.suspends(invokeThread)) {
              mySuspendManager.suspendThread(suspendingContext, invokeThread);
            }
          }
        }

        LOG.debug("getVirtualMachine().clearCaches()");
        invokeThread.getVirtualMachineProxy().clearCaches();
        afterMethodInvocation(suspendContext, internalEvaluate);

        myEvaluationDispatcher.getMulticaster().evaluationFinished(suspendContext);
      }
    }
E startInternal(boolean internalEvaluate, @NotNull ThreadReferenceProxyImpl invokeThread)