Repository: coder/code-server (TypeScript)
Date: 2023-06-20 20:46:46+00:00
Task type: Bug Fix

Issue:
[Bug]: Can't start 2 instances of code-server `4.14.0` for separate users
### Is there an existing issue for this?

- [X] I have searched the existing issues

### OS/Web Information

- Web Browser: Chrome
- Local OS: Ubuntu
- Remote OS: Windows
- Remote Architecture: amd64
- `code-server --version`: 4.14.0


### Steps to Reproduce

1. Run `/usr/bin/code-server` for user 1 - ok
2. Run `/usr/bin/code-server` for user 2 - fails with the following


### Expected

Both instances should start

### Actual

2nd instance fails to start

### Logs

2nd instance tries to open the same file

[2023-06-19T16:15:12.625Z] info  code-server 4.14.0 9955cd91a4ca17e47d205e5acaf4c342a917a5e9
[2023-06-19T16:15:12.626Z] info  Using user-data-dir ~/code-server/user
[2023-06-19T16:15:12.629Z] error EPERM: operation not permitted, unlink '/tmp/code-server-ipc.sock'


### Screenshot/Video

_No response_

### Does this issue happen in VS Code or GitHub Codespaces?

- [X] I cannot reproduce this in VS Code.
- [X] I cannot reproduce this in GitHub Codespaces.

### Are you accessing code-server over HTTPS?

- [X] I am using HTTPS.

### Notes

It seems that every instance is trying to write to `/tmp/code-server-ipc.sock`, this was not the case in `4.13.0`. Is there a way to specify an alternate socket file for each instance?


Issue discussion:
Having the same issue, this totally bricked our shared development environment.

**EDIT** For anyone else who ends up here, a downgrade worked for my environment.
Before we were writing to a file instead of a socket and I think 
we must have been ignoring write errors but with the new system we 
do not.  We may want to catch and warn about errors rather than 
hard failing.

Additionally we should use a unique socket path per user.

CC @sleexyz in case you have interest in fixing this, otherwise I 
believe I will have time next week.

@code-asher Ah darn, I'll take a stab rn.

Solution patch:
diff --git a/src/node/main.ts b/src/node/main.ts
--- a/src/node/main.ts
+++ b/src/node/main.ts
@@ -126,7 +126,9 @@ export const runCodeServer = async (
 
   logger.info(`Using config file ${humanPath(os.homedir(), args.config)}`)
   logger.info(`${protocol.toUpperCase()} server listening on ${serverAddress.toString()}`)
-  logger.info(`Session server listening on ${sessionServerAddress?.toString()}`)
+  if (sessionServerAddress) {
+    logger.info(`Session server listening on ${sessionServerAddress.toString()}`)
+  }
 
   if (args.auth === AuthType.Password) {
     logger.info("  - Authentication is enabled")
diff --git a/src/node/vscodeSocket.ts b/src/node/vscodeSocket.ts
--- a/src/node/vscodeSocket.ts
+++ b/src/node/vscodeSocket.ts
@@ -1,14 +1,13 @@
 import { logger } from "@coder/logger"
 import express from "express"
 import * as http from "http"
-import * as os from "os"
 import * as path from "path"
 import { HttpCode } from "../common/http"
 import { listen } from "./app"
-import { canConnect } from "./util"
+import { canConnect, paths } from "./util"
 
 // Socket path of the daemonized code-server instance.
-export const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "code-server-ipc.sock")
+export const DEFAULT_SOCKET_PATH = path.join(paths.data, `code-server-ipc.sock`)
 
 export interface EditorSessionEntry {
   workspace: {
@@ -78,7 +77,11 @@ export async function makeEditorSessionManagerServer(
   })
 
   const server = http.createServer(router)
-  await listen(server, { socket: codeServerSocketPath })
+  try {
+    await listen(server, { socket: codeServerSocketPath })
+  } catch (e) {
+    logger.warn(`Could not create socket at ${codeServerSocketPath}`)
+  }
   return server
 }
 


Test patch:
diff --git a/test/unit/node/vscodeSocket.test.ts b/test/unit/node/vscodeSocket.test.ts
--- a/test/unit/node/vscodeSocket.test.ts
+++ b/test/unit/node/vscodeSocket.test.ts
@@ -1,5 +1,50 @@
-import { EditorSessionManager } from "../../../src/node/vscodeSocket"
-import { clean, tmpdir, listenOn } from "../../utils/helpers"
+import { logger } from "@coder/logger"
+import * as app from "../../../src/node/app"
+import { paths } from "../../../src/node/util"
+import {
+  DEFAULT_SOCKET_PATH,
+  EditorSessionManager,
+  makeEditorSessionManagerServer,
+} from "../../../src/node/vscodeSocket"
+import { clean, tmpdir, listenOn, mockLogger } from "../../utils/helpers"
+
+describe("DEFAULT_SOCKET_PATH", () => {
+  it("should be a unique path per user", () => {
+    expect(DEFAULT_SOCKET_PATH.startsWith(paths.data)).toBe(true)
+  })
+})
+
+describe("makeEditorSessionManagerServer", () => {
+  let tmpDirPath: string
+
+  const testName = "mesms"
+
+  beforeAll(async () => {
+    jest.clearAllMocks()
+    mockLogger()
+    await clean(testName)
+  })
+
+  afterAll(() => {
+    jest.resetModules()
+  })
+
+  beforeEach(async () => {
+    tmpDirPath = await tmpdir(testName)
+  })
+
+  it("warns if socket cannot be created", async () => {
+    jest.spyOn(app, "listen").mockImplementation(() => {
+      throw new Error()
+    })
+    const server = await makeEditorSessionManagerServer(
+      `${tmpDirPath}/code-server-ipc.sock`,
+      new EditorSessionManager(),
+    )
+    expect(logger.warn).toHaveBeenCalledWith(`Could not create socket at ${tmpDirPath}/code-server-ipc.sock`)
+    server.close()
+  })
+})
 
 describe("EditorSessionManager", () => {
   let tmpDirPath: string