private static synchronized JavacServerClient ensureJavacServerLaunched(CompileContext context)
      throws Exception {
    final ExternalJavacDescriptor descriptor = ExternalJavacDescriptor.KEY.get(context);
    if (descriptor != null) {
      return descriptor.client;
    }
    // start server here
    final int port = findFreePort();
    final int heapSize = getJavacServerHeapSize(context);

    // defaulting to the same jdk that used to run the build process
    String javaHome = SystemProperties.getJavaHome();
    int javaVersion = convertToNumber(SystemProperties.getJavaVersion());

    for (JpsSdk<?> sdk : context.getProjectDescriptor().getProjectJavaSdks()) {
      final String version = sdk.getVersionString();
      final int ver = convertToNumber(version);
      if (ver > javaVersion) {
        javaVersion = ver;
        javaHome = sdk.getHomePath();
      }
    }

    final BaseOSProcessHandler processHandler =
        JavacServerBootstrap.launchJavacServer(
            javaHome, heapSize, port, Utils.getSystemRoot(), getCompilationVMOptions(context));
    final JavacServerClient client = new JavacServerClient();
    try {
      client.connect("127.0.0.1", port);
    } catch (Throwable ex) {
      processHandler.destroyProcess();
      throw new Exception("Failed to connect to external javac process: ", ex);
    }
    ExternalJavacDescriptor.KEY.set(context, new ExternalJavacDescriptor(processHandler, client));
    return client;
  }
  private boolean compileJava(
      final CompileContext context,
      ModuleChunk chunk,
      Collection<File> files,
      Collection<File> classpath,
      Collection<File> platformCp,
      Collection<File> sourcePath,
      DiagnosticOutputConsumer diagnosticSink,
      final OutputFileConsumer outputSink)
      throws Exception {

    final TasksCounter counter = new TasksCounter();
    COUNTER_KEY.set(context, counter);

    final JpsJavaExtensionService javaExt = JpsJavaExtensionService.getInstance();
    final JpsJavaCompilerConfiguration compilerConfig =
        javaExt.getCompilerConfiguration(context.getProjectDescriptor().getProject());
    assert compilerConfig != null;

    final Set<JpsModule> modules = chunk.getModules();
    ProcessorConfigProfile profile = null;
    if (modules.size() == 1) {
      final JpsModule module = modules.iterator().next();
      profile = compilerConfig.getAnnotationProcessingProfile(module);
    } else {
      // perform cycle-related validations
      Pair<String, LanguageLevel> pair = null;
      for (JpsModule module : modules) {
        final LanguageLevel moduleLevel = javaExt.getLanguageLevel(module);
        if (pair == null) {
          pair = Pair.create(module.getName(), moduleLevel); // first value
        } else {
          if (!Comparing.equal(pair.getSecond(), moduleLevel)) {
            final String message =
                "Modules "
                    + pair.getFirst()
                    + " and "
                    + module.getName()
                    + " must have the same language level because of cyclic dependencies between them";
            diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, message));
            return true;
          }
        }
      }

      // check that all chunk modules are excluded from annotation processing
      for (JpsModule module : modules) {
        final ProcessorConfigProfile prof = compilerConfig.getAnnotationProcessingProfile(module);
        if (prof.isEnabled()) {
          final String message =
              "Annotation processing is not supported for module cycles. Please ensure that all modules from cycle ["
                  + chunk.getName()
                  + "] are excluded from annotation processing";
          diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, message));
          return true;
        }
      }
    }

    final Map<File, Set<File>> outs = buildOutputDirectoriesMap(context, chunk);
    final List<String> options = getCompilationOptions(context, chunk, profile);
    final ClassProcessingConsumer classesConsumer =
        new ClassProcessingConsumer(context, outputSink);
    if (LOG.isDebugEnabled()) {
      LOG.debug(
          "Compiling chunk ["
              + chunk.getName()
              + "] with options: \""
              + StringUtil.join(options, " ")
              + "\"");
    }
    try {
      final boolean rc;
      if (USE_EMBEDDED_JAVAC) {
        final boolean useEclipse = useEclipseCompiler(context);
        rc =
            JavacMain.compile(
                options,
                files,
                classpath,
                platformCp,
                sourcePath,
                outs,
                diagnosticSink,
                classesConsumer,
                context.getCancelStatus(),
                useEclipse);
      } else {
        final JavacServerClient client = ensureJavacServerLaunched(context);
        final RequestFuture<JavacServerResponseHandler> future =
            client.sendCompileRequest(
                options,
                files,
                classpath,
                platformCp,
                sourcePath,
                outs,
                diagnosticSink,
                classesConsumer);
        while (!future.waitFor(100L, TimeUnit.MILLISECONDS)) {
          if (context.getCancelStatus().isCanceled()) {
            future.cancel(false);
          }
        }
        rc = future.getMessageHandler().isTerminatedSuccessfully();
      }
      return rc;
    } finally {
      counter.await();
    }
  }