private boolean doQueue(
      @NotNull Document document,
      @NotNull Project project,
      CommitStage start,
      @NonNls @NotNull Object reason) {
    synchronized (documentsToCommit) {
      if (!changeCommitStage(document, start, CommitStage.QUEUED_TO_COMMIT, false)) return false;

      Object[] documentTasks = documentsToCommit.toArray();
      for (Object o : documentTasks) {
        assert o != null : "Null element in:" + documentsToCommit;
        CommitTask task = (CommitTask) o;
        if (task.document == document) {
          ProgressIndicator current = document.getUserData(COMMIT_PROGRESS);
          if (current == null) {
            // already queued, not started yet
            return true;
          } else {
            // cancel current commit process to re-queue
            current.cancel();
            removeCommitFromQueue(document);
            break;
          }
        }
      }
      ProgressIndicator indicator = new ProgressIndicatorBase();
      indicator.start();
      documentsToCommit.addLast(new CommitTask(document, project, indicator, reason));
      log("Queued", document, false, reason);
      wakeUpQueue();
      return true;
    }
  }
 private void stopThread() {
   isDisposed = true;
   synchronized (documentsToCommit) {
     documentsToCommit.clear();
   }
   cancel("Stop thread");
   wakeUpQueue();
   while (!threadFinished) {
     wakeUpQueue();
     synchronized (documentsToCommit) {
       try {
         documentsToCommit.wait(10);
       } catch (InterruptedException ignored) {
       }
     }
   }
 }
 @TestOnly
 public void clearQueue() {
   synchronized (documentsToCommit) {
     documentsToCommit.clear();
   }
   clearLog();
   disable("end of test");
   wakeUpQueue();
 }
  private void removeCommitFromQueue(@NotNull Document document) {
    synchronized (documentsToCommit) {
      ProgressIndicator indicator = document.getUserData(COMMIT_PROGRESS);

      if (indicator != null && indicator.isRunning()) {
        indicator.stop(); // mark document as removed

        log("Removed from queue", document, false);
      }
      // let our thread know that queue must be polled again
      wakeUpQueue();
    }
  }
 public void enable(Object reason) {
   myEnabled = true;
   wakeUpQueue();
   log("Enabled", null, false, reason);
 }
  @Override
  public void run() {
    threadFinished = false;
    while (!isDisposed) {
      try {
        boolean success = false;
        Document document = null;
        Project project = null;
        ProgressIndicator indicator = null;
        try {
          CommitTask task;
          synchronized (documentsToCommit) {
            if (!myEnabled || documentsToCommit.isEmpty()) {
              documentsToCommit.wait();
              continue;
            }
            task = documentsToCommit.pullFirst();
            document = task.document;
            indicator = task.indicator;
            project = task.project;

            log("Pulled", document, false, indicator);

            CommitStage commitStage = getCommitStage(document);
            Document[] uncommitted = null;
            if (commitStage != CommitStage.QUEUED_TO_COMMIT
                || project.isDisposed()
                || !ArrayUtil.contains(
                    document,
                    uncommitted =
                        PsiDocumentManager.getInstance(project).getUncommittedDocuments())) {
              List<Document> documents = uncommitted == null ? null : Arrays.asList(uncommitted);
              log("Abandon and proceeding to next", document, false, commitStage, documents);
              continue;
            }
            if (indicator.isRunning()) {
              useIndicator(indicator);
              document.putUserData(COMMIT_PROGRESS, indicator);
            } else {
              success = true; // document has been marked as removed, e.g. by synchronous commit
            }
          }

          Runnable finishRunnable = null;
          if (!success && !indicator.isCanceled()) {
            try {
              finishRunnable = commit(document, project, null, indicator, false, task.reason);
              success = finishRunnable != null;
              log("DCT.commit returned", document, false, finishRunnable, indicator);
            } finally {
              document.putUserData(COMMIT_PROGRESS, null);
            }
          }

          synchronized (documentsToCommit) {
            if (indicator.isCanceled()) {
              success = false;
            }
            if (success) {
              assert !ApplicationManager.getApplication().isDispatchThread();
              UIUtil.invokeLaterIfNeeded(finishRunnable);
              log(
                  "Invoked later finishRunnable",
                  document,
                  false,
                  success,
                  finishRunnable,
                  indicator);
            }
          }
        } catch (ProcessCanceledException e) {
          cancel(e); // leave queue unchanged
          log("PCE", document, false, e);
          success = false;
        } catch (InterruptedException e) {
          // app must be closing
          int i = 0;
          log("IE", document, false, e);
          cancel(e);
        } catch (Throwable e) {
          LOG.error(e);
          cancel(e);
        }
        synchronized (documentsToCommit) {
          if (!success && indicator.isRunning()) { // running means sync commit has not intervened
            // reset status for queue back successfully
            changeCommitStage(
                document, CommitStage.WAITING_FOR_PSI_APPLY, CommitStage.QUEUED_TO_COMMIT, false);
            changeCommitStage(document, CommitStage.COMMITTED, CommitStage.QUEUED_TO_COMMIT, false);
            doQueue(document, project, CommitStage.QUEUED_TO_COMMIT, "re-added on failure");
          }
        }
      } catch (Throwable e) {
        e.printStackTrace();
        // LOG.error(e);
      }
    }
    threadFinished = true;
    // ping the thread waiting for close
    wakeUpQueue();
    log("Good bye", null, false);
  }