···11+---
22+'moroutine': minor
33+---
44+55+New subpath `moroutine/serve`: fd-fanout server primitive
66+77+Scale an ordinary Node `http.Server` across worker threads by passing raw socket fds from the main thread to workers. Each worker runs its own server; main just accepts connections and routes them. Includes `leastConns()` (default) and `roundRobin()` balance strategies, graceful shutdown via `server.close()`, and composition with any framework that exposes an `http.Server` (express, fastify, etc.). POSIX-only.
88+99+```ts
1010+// worker
1111+export const runServer = mo(import.meta, async (...args: ListenArgs): Promise<void> => {
1212+ const srv = createServer((req, res) => {
1313+ res.end('hi');
1414+ });
1515+ await listen(srv, ...args);
1616+});
1717+1818+// main
1919+await using run = workers(4);
2020+using threads = serverThreads(run.workers, server);
2121+await run(threads.map(([w, args]) => assign(w, runServer(...args))));
2222+```
+52
README.md
···366366console.log(pos.load()); // { x: 1, y: 1 }
367367```
368368369369+## Server Threads
370370+371371+Scale a Node HTTP server across worker threads by fanning out socket fds. Main accepts TCP connections; workers each run their own `http.Server` and handle requests. Imported from the `moroutine/serve` subpath.
372372+373373+```ts
374374+// app-server.ts
375375+import { createServer } from 'node:http';
376376+import { mo } from 'moroutine';
377377+import { listen, type ListenArgs } from 'moroutine/serve';
378378+379379+export const runServer = mo(import.meta, async (...args: ListenArgs): Promise<void> => {
380380+ const srv = createServer((req, res) => {
381381+ res.writeHead(200);
382382+ res.end('hello');
383383+ });
384384+ await listen(srv, ...args);
385385+});
386386+```
387387+388388+```ts
389389+// main.ts
390390+import { createServer } from 'node:net';
391391+import { workers, assign } from 'moroutine';
392392+import { serverThreads } from 'moroutine/serve';
393393+import { runServer } from './app-server.ts';
394394+395395+const server = createServer();
396396+server.listen(3000);
397397+process.on('SIGINT', () => server.close());
398398+399399+{
400400+ await using run = workers(4);
401401+ using threads = serverThreads(run.workers, server);
402402+ await run(threads.map(([w, args]) => assign(w, runServer(...args))));
403403+}
404404+```
405405+406406+Connection routing defaults to `leastConns()`; pass `{ balance: roundRobin() }` for simple fan-out. TLS termination runs on workers (`https.createServer` / `tls.createServer` inside your moroutine); the main-thread listener stays raw TCP. POSIX-only (Linux, macOS). Frameworks that expose an underlying `http.Server` compose without glue:
407407+408408+```ts
409409+// express
410410+const srv = createServer(express());
411411+await listen(srv, ...args);
412412+413413+// fastify
414414+const app = fastify();
415415+await app.ready();
416416+await listen(app.server, ...args);
417417+```
418418+419419+Graceful shutdown: call `server.close()` on the main listener; the cascade closes worker channels, drains in-flight requests (up to `drainTimeout`, default 30s), and resolves the moroutines. Combine with `await using run = workers(...)` for full pool teardown.
420420+369421## Streaming
370422371423### Streaming Moroutines