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

Implement Bun.serve({fd}) and Bun.listen({fd}) for socket-activated deployments #2852

Closed
wants to merge 1 commit into from

Conversation

shivak
Copy link

@shivak shivak commented May 11, 2023

Overview. This PR implements Bun.serve({fd}) and Bun.listen({fd}), which runs the HTTP (or TCP) server on a socket that has are already been bound and listened. The overall idea is to have a separate process bind() and listen() to a socket, and pass it to the Bun process. This is used in production deployments for:

  • efficiency: servers can be started on demand in, e.g., large multi-tenant edge setups ☺
  • security: a privileged process can bind port < 1024 and then pass the socket to an unprivileged server. Also, the socket can be passed into a restricted network namespace wherein the server is run. This allows the server to respond to requests, but blocks all other network activity.
  • reliability: socket activation can implement failover.

This feature is available in Go, Node, etc but not Deno (denoland/deno#6529). Here, it's mildly tricky to implement, since it involves changes to two layers of low-level platform bindings - see the companion PRs at the bottom.

Usage. Typical users (on Linux/systemd) would call Bun.serve({fd: process.env.LISTEN_FDS}). On Mac/launchd, the fd is obtained from launch_activate_socket. On Windows, it could ostensibly be transferred by WSADuplicateSocket.

Workaround. socat or systemd-socket-proxyd. These aren't ideal for efficiency, security, or reliability.

Notes.

  1. The Zig and C++ code correctly handle the socket type, which is usually just an fd, but isn't on Windows. The Javascript API is simple and oblivious, expecting positive ints to be passed.

  2. This introduces an asymmetry between listen() and connect(), since now you can listen() to an fd, but you cannot connect() to one. Hence the somewhat awkward FdOrUnixOrHost type, which I don't think is worth fretting over.

  3. Testing requires some Zig code to perform the bind() and listen() typically done out-of-process.

  4. I copied the library changes to _libusockets.h and libuwsockets.cpp. These seem to differ in minor (unnecessary?) ways from the underlying libraries, for reasons I don't currently understand.

  5. I haven't tried running this on Windows. Also, as @Jarred-Sumner mentioned, some sanitization of fd might be useful. Also, I've never actually written any Zig code, so maybe double-check things.

Building. For politeness, I didn't redirect any submodules. Wait for the following PRs to be merged, or use my feature branches.

…ed deployments

Make use of new functionality in uWebSockets and uSockets, which allow servers to
attach to sockets that have already been bound and listened, rather than themselves
binding and listening to a port.
@shivak
Copy link
Author

shivak commented May 12, 2023

Aside: this is probably the safest way to deploy with good multicore performance. The key is SO_REUSEPORT, which Bun uses by default. But there are downsides to making each Bun worker handle its own socket binding:

With fd, the privileged monitor gets sockets and passes them to only legitimate workers. With multiple workers, a monitor process is anyways necessary, since crashed workers need to be detected and restarted.

@Jarred-Sumner
Copy link
Collaborator

Jarred-Sumner commented May 12, 2023 via email

@cirospaciari cirospaciari self-requested a review May 12, 2023 17:47
@cirospaciari
Copy link
Member

cirospaciari commented May 12, 2023

  1. The Zig and C++ code correctly handle the socket type, which is usually just an fd, but isn't on Windows. The Javascript API is simple and oblivious, expecting positive ints to be passed.

Probably we will need an abstraction here when Windows support for Runtime release.

  1. This introduces an asymmetry between listen() and connect(), since now you can listen() to an fd, but you cannot connect() to one. Hence the somewhat awkward FdOrUnixOrHost type, which I don't think is worth fretting over.

The most common use case should be listen() we can work on connect() later if requested.

  1. I copied the library changes to _libusockets.h and libuwsockets.cpp. These seem to differ in minor (unnecessary?) ways from the underlying libraries, for reasons I don't currently understand.

This API diverged slightly from the current C API of WebSockets because the C API itself was not done yet when Bun started to use it, and Bun required some changes to achieve some features (like sendfile) and add fixes. So uws.zig bindings actually use libuwsockets.cpp not the capi/libuwebsockets.cpp

  1. I haven't tried running this on Windows. Also, as @Jarred-Sumner mentioned, some sanitization of fd might be useful. Also, I've never actually written any Zig code, so maybe double-check things.

I'm working on Windows support, this can be added later.

Building. For politeness, I didn't redirect any submodules. Wait for the following PRs to be merged, or use my feature branches.

I take a look at uSockets and uWebSockets PR's and looks to be great. Probably we will need more testing like listening to the same socket twice on the same loop.

LGTM, thanks @shivak

@shivak
Copy link
Author

shivak commented Aug 5, 2023

This PR touches some critical codepaths (creating any kind of server) and the underlying libs, so it is a good candidate to merge before a stable release. In the next few days, I'll resolve the conflicts that have arisen.

@infrahead
Copy link

@Jarred-Sumner Is the plan for this to be merged still? I'm hoping to migrate my Node.js web services to Bun, but using systemd sockets is how I architect things for zero-downtime deployments. Alternatively I can look into SO_REUSEPORT which I know works with uWS, which on modern Linux kernels might also be part of a solution for zero-downtime setup, but before I look into it was curious on when might get fd support.

@karimfromjordan
Copy link

I would also like to express my interest in this feature. In Node.js this isn't an issue.

@Electroid Electroid marked this pull request as draft January 25, 2024 01:02
@Jarred-Sumner
Copy link
Collaborator

I would like for us to do this, but I think it'll need a new PR due to bitrot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants