microsoft / vscode-gradle

Manage Gradle Projects, run Gradle tasks and provide better Gradle file authoring experience in VS Code
https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-gradle
MIT License
124 stars 51 forks source link

[Unsolved] Merge both server & Named pipe #1504

Closed Jiaaming closed 3 months ago

Jiaaming commented 3 months ago

Hi @jdneo

New Architect:

new Design

cooperate with https://github.com/microsoft/build-server-for-gradle/pull/155

This Draft PR include following change:

  1. Merge Gradle Server(aka. Task Server) and Build Server. Now they will start as two threads in one process. It works find for now.
  2. For the communication between Build Server and Build Client, I use Named pipe following this PR[https://github.com/eclipse-jdtls/eclipse.jdt.ls/pull/2922] in JDT.LS. Due to the different OS behavior between Windows and Unix, Windows use AsynchronousFileChannel while Unix use UnixSocket. The Unix part works fine✅ but the Windows part not work ❌.

If you run the code, it will stop at the importToWorkSpace stage

InitializeBuildResult initializeResult = buildServer.buildInitialize(params).join();

In the Language Support For Java under Output, it will keep printing build/initialize message until out of memory. Error message like this:

Screenshot 2024-06-20 at 22 09 02

In my code, I add build-server-for-gradle/.../ServerNamedPipeStream.java and vscode-gradle/.../ClientNamedPIpeStream.java In order to get InputStream and OutputStream for Launcher in both build-server-for-gradle/Launcher.java and vscode-gradle/importerPlugin.java.

In these two files, Only private void initializeNamedPipe() are different. Other functions are copied from previous PR implementation

Note: I hardcode for now and you can switch it to your own file path. In the end we hope to achieve this by calling VScode command.

In vscode-gradle/importerPlugin/../ClientNamedPipeStream.java:


private void initializeNamedPipe() {
    File pipeFile = new File("/tmp/example.sock");
    if (isWindows()) {
    pipeFile = new File("/tmp/example");
        AsynchronousFileChannel channel = null;
        try {
            channel = AsynchronousFileChannel.open(pipeFile.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE);
            input = new NamedPipeInputStream(channel);
            output = new NamedPipeOutputStream(channel);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return;
    }

    boolean connected = false;
    while (!connected) {
        {
            try {
                UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(pipeFile.toPath());
                SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX);
                channel.connect(socketAddress);
                input = new NamedPipeInputStream(channel);
                output = new NamedPipeOutputStream(channel);
                connected = true;
            } catch (IOException e) {
                try {
                    System.out.println("Failed to connect. Retrying in 1 second...");
                    Thread.sleep(1000);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("Thread interrupted while trying to connect to named pipe", ie);
                }
            }
        }

    }
}

In build-Server-for-gradle/ServerNamePipeStream.java:

 private void initializeNamedPipe() {
      File pipeFile = new File("/tmp/example.sock");

      if (isWindows()) {
        try {
          pipeFile = new File("/tmp/example");
          AsynchronousFileChannel clientChannel = AsynchronousFileChannel.open(
              pipeFile.toPath(),
              StandardOpenOption.CREATE,
              StandardOpenOption.READ,
              StandardOpenOption.WRITE);
          input = new NamedPipeInputStream(clientChannel);
          output = new NamedPipeOutputStream(clientChannel);
        } catch (IOException e) {
          e.printStackTrace();
        }
      } else {
        if (pipeFile.exists()) {
          pipeFile.delete();
        }
        UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(pipeFile.toPath());
        try {
          ServerSocketChannel serverChannel = ServerSocketChannel
              .open(StandardProtocolFamily.UNIX);
          serverChannel.bind(socketAddress);
          SocketChannel clientChannel = serverChannel.accept();
          input = new NamedPipeInputStream(clientChannel);
          output = new NamedPipeOutputStream(clientChannel);
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

My concerns:

  1. I believe their remain some misunderstand here because even if I switch these two initializeNamedPipe() function, they will act exactly the same(UnixSocket and build Server still works). Is this what we expect? There are many Client & Server in this whole architecture that made me quite confused.
  2. I think it also related with lsp/Launcher since the only change I made is the Input&OutputStream parameters:
    org.eclipse.lsp4j.jsonrpc.Launcher<BuildClient> launcher = new 
          org.eclipse.lsp4j.jsonrpc.Launcher.Builder<BuildClient>()
          .setOutput(pipeStream.getOutputStream()) //here
          .setInput(pipeStream.getInputStream()) //here