Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bun.serve does not work with unix socket #8044

Closed
jefer94 opened this issue Jan 8, 2024 · 4 comments
Closed

Bun.serve does not work with unix socket #8044

jefer94 opened this issue Jan 8, 2024 · 4 comments
Labels
bug Something isn't working bun.js Something to do with a Bun-specific API

Comments

@jefer94
Copy link

jefer94 commented Jan 8, 2024

What version of Bun is running?

1.0.21+837cbd60d

What platform is your computer?

Linux 6.6.10-zen1-1-zen x86_64 unknown

What steps can reproduce the bug?

// server.ts bun implementation
async function bun(): Promise<void> {
  const Bun = await import("bun");
  await setup();

  const server = Bun.serve({
    unix: socketPath,
    async fetch(req) {
      try {
        const buffer = Buffer.from(await req.arrayBuffer());

        const decodedMessage = Request.decode(buffer);
        const decodedData = Request.toObject(decodedMessage);

        const res = await rpc(decodedData);

        const response = new Response(JSON.stringify(res));
        response.headers.set("Content-Type", "application/json");

        // const message = ResponseType.create(res);
        // const buffer = ResponseType.encode(message).finish();

        return response;
      } catch (err) {
        throw err; // Rethrow the error to prevent silent failures
      }
    },
  });

  console.log(`Server listening on unix://${socketPath}!`);
  await server;
}

```ts
// client.ts
async function render(
  html: string,
  dependencies: Dependency[] = [],
  timeout: number = 5000
): Promise<any> {
  const { promise, resolve, reject } = Promise.withResolvers<any>();
  let socket: net.Socket;
  try {
    socket = net.connect("/tmp/lit-ssr.sock");
  } catch (e) {
    reject(e);
    return;
  }
  const cancelId = setTimeout(() => {
    socket.end();
    reject(new Error("Timeout"));
  }, timeout);

  const body = {
    html,
    dependencies,
  };
  const errMsg = Request.verify(body);
  if (errMsg) {
    socket.end();
    reject(errMsg);
    return promise;
  } else {
    const message = Request.create(body);
    const buffer = Request.encode(message).finish();
    socket.write(buffer);
  }

  socket.on("data", (data) => {
    const decodedMessage = Response.decode(data);
    const decodedData = Response.toObject(decodedMessage);

    resolve(decodedData);
    clearTimeout(cancelId);
    socket.end();
  });
  socket.on("error", (err) => {
    reject(err);
    clearTimeout(cancelId);
    socket.end();
  });
  socket.on("close", () => {
    reject(new Error("Socket closed"));
    clearTimeout(cancelId);
    socket.end();
  });

  return promise;
}

What is the expected behavior?

That my client could connect to my socket

What do you see instead?

A socket timeout

Additional information

I'm using protobufjs to serialize the response, but as can you see it's wrapped in a try catch

When I ran the node implementation I got

To reproduce this error you must use wrk, if you only do a few fetch requests this error does not happen

#  wrk -t20 -c2000 -d30s http://localhost:4000/new2 
Running 30s test @ http://localhost:4000/new2
  20 threads and 2000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   146.79ms   82.48ms 412.57ms   88.38%
    Req/Sec   221.45    155.36     0.93k    64.29%
  4537 requests in 30.10s, 19.53MB read
  Socket errors: connect 0, read 4514, write 25864468, timeout 0
  Non-2xx or 3xx responses: 60
Requests/sec:    150.73
Transfer/sec:    664.51KB
# server.ts
ENOENT: No such file or directory
   errno: -2
 syscall: "unlink"
# client.ts
error2 27 |   timeout: number = 5000
28 | ): Promise<any> {
29 |   const { promise, resolve, reject } = Promise.withResolvers<any>();
30 |   let socket: net.Socket;
31 |   try {
32 |     socket = net.connect("/tmp/lit-ssr.sock");
                  ^
ENOENT: Failed to connect
 syscall: "connect"

      at connect (node:net:257:19)
      at /home/jefer/dev/experiments/x/index.ts:32:14
      at render (/home/jefer/dev/experiments/x/index.ts:25:3)
      at /home/jefer/dev/experiments/x/index.ts:161:24
      at processTicksAndRejections (:61:77)

fish: Job 1, 'bun index.ts' terminated by signal SIGSEGV (Address boundary error)
// server.ts node implementation
async function node(): Promise<void> {
  const net = await import("net");
  await setup();

  const server = net.createServer((socket) => {
    socket.on("data", async (data) => {
      const decodedMessage = Request.decode(data);
      const decodedData = Request.toObject(decodedMessage);

      const res = await rpc(decodedData);

      const message = ResponseType.create(res);
      const buffer = ResponseType.encode(message).finish();
      socket.write(buffer);
    });

    socket.on("end", () => {});

    socket.on("error", (err) => {
      console.error("Socket error:", err);
    });
  });

  server.listen(socketPath, () => {
    console.log(`Server listening on unix://${socketPath}!`);
  });
}
@jefer94 jefer94 added the bug Something isn't working label Jan 8, 2024
@Electroid
Copy link
Contributor

Does this issue still reproduce if it's just a simple hello-world server on the unix socket?

@Electroid Electroid added the bun.js Something to do with a Bun-specific API label Jan 8, 2024
@jefer94
Copy link
Author

jefer94 commented Jan 8, 2024

Yes

// server.ts
import fs from "fs/promises";

const socketPath = "/tmp/lit-ssr.sock";

async function main(): Promise<void> {
  // Delete the socket file if it already exists
  if (await fs.exists(socketPath)) {
    await fs.unlink(socketPath);
  }

  const [exec, ..._] = process.argv;

  if (exec.indexOf("node") !== -1) {
    node();
  } else if (exec.indexOf("bun") !== -1) {
    // node();
    // FIXME: This doesn't work yet
    bun();
  } else {
    throw Error("Runtime not supported yet");
  }
}

async function node(): Promise<void> {
  const net = await import("net");

  const server = net.createServer((socket) => {
    socket.on("data", async (data) => {
      socket.write("Hello World");
    });

    socket.on("end", () => {});

    socket.on("error", (err) => {
      console.error("Socket error:", err);
    });
  });

  server.listen(socketPath, () => {
    console.log(`Server listening on unix://${socketPath}!`);
  });
}

// FIXME: This doesn't work yet
async function bun(): Promise<void> {
  const Bun = await import("bun");

  const server = Bun.serve({
    unix: socketPath,
    async fetch(req) {
      try {
        const response = new Response("Hello World");
        response.headers.set("Content-Type", "text/txt");

        return response;
      } catch (err) {
        throw err; // Rethrow the error to prevent silent failures
      }
    },
  });

  console.log(`Server listening on unix://${socketPath}!`);
  await server;
}

main();
// client.ts
import net from "net";

type Dependency = { code: string; ext: string };

async function render(timeout: number = 5000): Promise<any> {
  const { promise, resolve, reject } = Promise.withResolvers<any>();
  const socket = net.connect("/tmp/lit-ssr.sock");
  const cancelId = setTimeout(() => {
    socket.end();
    reject(new Error("Timeout"));
  }, timeout);

  const body = "Hello";
  socket.write(body);

  socket.on("data", (data) => {
    resolve(data);
    clearTimeout(cancelId);
    socket.end();
  });
  socket.on("error", (err) => {
    reject(err);
    clearTimeout(cancelId);
    socket.end();
  });
  socket.on("close", () => {
    reject(new Error("Socket closed"));
    clearTimeout(cancelId);
    socket.end();
  });

  return promise;
}

async function main(): Promise<void> {
  console.log(await render(1000));
  console.log("done");
}
main();

@jefer94
Copy link
Author

jefer94 commented Jan 8, 2024

Also should be fine can fetch from an unix socket without using the node api

@Jarred-Sumner
Copy link
Collaborator

Bun.serve() refers to an HTTP server. This example code is not a valid HTTP request. So this will always fail.

  const body = "Hello";
  socket.write(body);

This CLI command does not send requests to a unix domain socket, this sends to an http request localhost:4000

wrk -t20 -c2000 -d30s http://localhost:4000/new2

If we instead use oha to send http requests to the example server above, on an M1 mac we get about 220,000 requests per second:

 oha http://localhost:3001 -n 400000 -c 10 --unix-socket=/tmp/lit-ssr.sock -d '{"hello": 123}' -m=POST
Summary:
  Success rate:	100.00%
  Total:	1.7615 secs
  Slowest:	0.0013 secs
  Fastest:	0.0000 secs
  Average:	0.0000 secs
  Requests/sec:	227079.3121

That being said, the crash you ran into is clearly a bug regardless - but I am not able to reproduce it. Are you still able to reproduce it in Bun v1.0.30? Is there anything else that needs to be done to reproduce the crash?

I'm going to close this as "not planned" for now since the example code doesn't reproduce the issue, but I will be happy to re-open this issue if you continue to run into it and give us an example that reproduces

@Jarred-Sumner Jarred-Sumner closed this as not planned Won't fix, can't repro, duplicate, stale Mar 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working bun.js Something to do with a Bun-specific API
Projects
None yet
Development

No branches or pull requests

3 participants