private async initServer()

in src/extension/src/lspclient.ts [236:319]


  private async initServer(): Promise<connectionInfo | undefined> {
    let releaseLock: any
    try {
      // Use a lockfile to coordianate between multiple VS Code windows.
      // Only the first process to acquire the lock needs to start the server.
      releaseLock = await lockfile.lock(LAUNCH_LOCKFILE, {
        realpath: false,
        stale: LAUNCH_LOCKFILE_TIMEOUT,
      })
    } catch (err) {
      if ((err as {code: string}).code != 'ELOCKED') {
        throw err
      }

      // If the lockfile is already held, wait for the other process to finish starting the server.
      this.outputChannelClient?.appendLine(
        'INFO: Launch detected via another VS Code window. Pausing to allow it to complete.'
      )

      await new Promise(resolve => setTimeout(resolve, LAUNCH_LOCKFILE_TIMEOUT))
      return await this.getExistingServerAddress()
    }

    if (this.outputChannelServerInit === undefined)
      this.outputChannelServerInit = vscode.window.createOutputChannel(
        'Uber LSP Server Initialization'
      )
    else this.outputChannelServerInit.appendLine('\n\n==== NEW SERVER ====')
    this.outputChannelClient?.appendLine(
      'INFO: Initializing a new server process. See output in "Uber LSP Server Initialization" channel.'
    )

    // Bazel depends on the parent process environment, as well as additional ULSP_CONFIG_DIR needed to launch service.
    const currentEnv = {...process.env}
    currentEnv.ULSP_CONFIG_DIR = this.serverLaunchConfig?.configDir

    // Process will be detached, so write stdout and stderr to a temporary file.
    const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ulsp-'))
    const tmpFileName = path.join(tmpDir, 'log.txt')
    this.outputChannelServerInit.appendLine(
      `Server output file: ${tmpFileName}`
    )
    await execAsync(`touch ${tmpFileName}`)

    // Support both MacOS and Linux
    const fullCmd =
      process.platform === 'darwin'
        ? `nohup ${this.serverLaunchConfig?.cmd} >${tmpFileName} 2>&1 &`
        : `setsid ${this.serverLaunchConfig?.cmd} >${tmpFileName} 2>&1 &`

    // Wait for the server to indicate readiness.
    // Reject if server does not indicate readiness within timeout.
    await new Promise<void>((resolve, reject) => {
      setTimeout(async () => {
        await releaseLock()
        reject(
          Error(
            'Language server start exceeded timeout, see "Uber LSP Server Initialization" output panel for details'
          )
        )
      }, 30000)

      const tail = new Tail(tmpFileName)
      tail.on('line', async (line: string) => {
        this.outputChannelServerInit?.appendLine(line)
        if (this.serverLaunchConfig && line.includes(this.serverLaunchConfig.serverReadyLine)) {
          await releaseLock()
          resolve()
        }
      })

      const serverProcess = cp.spawn(fullCmd, {
        env: currentEnv,
        detached: true,
        shell: true,
        stdio: 'ignore',
      })

      serverProcess.unref()
    })

    // After server has been started, check its output file for connection info.
    return await this.getExistingServerAddress()
  }