@Override
    public void channelRead0(final ChannelHandlerContext context, JavacRemoteProto.Message message) throws Exception {
      final UUID sessionId = JavacProtoUtil.fromProtoUUID(message.getSessionId());
      final JavacRemoteProto.Message.Type messageType = message.getMessageType();

      JavacRemoteProto.Message reply = null;

      try {
        if (messageType == JavacRemoteProto.Message.Type.REQUEST) {
          final JavacRemoteProto.Message.Request request = message.getRequest();
          final JavacRemoteProto.Message.Request.Type requestType = request.getRequestType();
          if (requestType == JavacRemoteProto.Message.Request.Type.COMPILE) {
            if (myCanceled.putIfAbsent(sessionId, Boolean.FALSE) == null) { // if not running yet
              final List<String> options = request.getOptionList();
              final List<File> files = toFiles(request.getFileList());
              final List<File> cp = toFiles(request.getClasspathList());
              final List<File> platformCp = toFiles(request.getPlatformClasspathList());
              final List<File> srcPath = toFiles(request.getSourcepathList());

              final ModulePath.Builder modulePathBuilder = ModulePath.newBuilder();
              final Map<String, String> namesMap = request.getModuleNamesMap();
              for (String path : request.getModulePathList()) {
                modulePathBuilder.add(namesMap.get(path), new File(path));
              }
              final ModulePath modulePath = modulePathBuilder.create();

              final List<File> upgradeModulePath = toFiles(request.getUpgradeModulePathList());

              final Map<File, Set<File>> outs = new HashMap<>();
              for (JavacRemoteProto.Message.Request.OutputGroup outputGroup : request.getOutputList()) {
                final Set<File> srcRoots = new HashSet<>();
                for (String root : outputGroup.getSourceRootList()) {
                  srcRoots.add(new File(root));
                }
                outs.put(new File(outputGroup.getOutputRoot()), srcRoots);
              }
              myThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                  boolean keepRunning = myKeepRunning;
                  try {
                    final JavacRemoteProto.Message result = compile(context, sessionId, options, files, cp, platformCp, modulePath, upgradeModulePath, srcPath, outs, new CanceledStatus() {
                      @Override
                      public boolean isCanceled() {
                        return Boolean.TRUE.equals(myCanceled.get(sessionId));
                      }
                    });
                    context.channel().writeAndFlush(result).awaitUninterruptibly();
                  }
                  catch (Throwable throwable) {
                    // in case of unexpected exception exit, to ensure the process is not stuck in a problematic state
                    keepRunning = false;
                    throwable.printStackTrace(System.err);
                    try {
                      // attempt to report via proto
                      context.channel().writeAndFlush(JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createFailure(throwable.getMessage(), throwable)));
                    }
                    catch (Throwable ignored) {
                    }
                  }
                  finally {
                    myCanceled.remove(sessionId); // state cleanup
                    if (keepRunning) {
                      JavacMain.clearCompilerZipFileCache();
                      //noinspection CallToSystemGC
                      System.gc();
                    }
                    else {
                      // in this mode this is only one-time compilation process that should stop after build is complete
                      ExternalJavacProcess.this.stop();
                    }
                    Thread.interrupted(); // reset interrupted status
                  }
                }
              });
            }
          }
          else if (requestType == JavacRemoteProto.Message.Request.Type.CANCEL){
            cancelBuild(sessionId);
          }
          else if (requestType == JavacRemoteProto.Message.Request.Type.SHUTDOWN){
            // cancel all running builds
            // todo: optionally wait for all builds to complete and only then shutdown
            for (UUID uuid : myCanceled.keySet()) {
              // todo: do we really need to wait for cancelled sessions to terminate?
              cancelBuild(uuid);
            }
            new Thread("StopThread") {
              @Override
              public void run() {
                ExternalJavacProcess.this.stop();
              }
            }.start();
          }
          else {
            reply = JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createFailure("Unsupported request type: " + requestType.name(), null));
          }
        }
        else {
          reply = JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createFailure("Unsupported message: " + messageType.name(), null));
        }
      }
      finally {
        if (reply != null) {
          context.channel().writeAndFlush(reply);
        }
      }
    }
@Override
    public void channelRead0(final ChannelHandlerContext context, JavacRemoteProto.Message message)