Breeze is an event-driven HTTP framework for Go built on gnet. Zero-allocation hot paths, a composable middleware chain, native WebSocket support with a built-in hub, and Swagger docs — out of the box.
Not another wrapper around net/http. Breeze is built from the ground up on a non-blocking event loop with an obsession over allocations.
[16]string. Param indexes are pre-computed at registration. The hot loop never allocates on a miss.unsafe.String converts raw bytes to strings without copying. Header keys are lowercased in-place. No bytes.Split — just indexed scans./swagger.Context. One-line save-to-disk API. No stdlib http.Request required.SetHeader upgrades to a private copy lazily — zero allocation unless you mutate.Measured on a simple JSON endpoint, same machine, same payload. The gap widens further under pipelining and high concurrency.
* Approximate. Run benchmarks in your environment for authoritative numbers.
Every middleware is a HandlerFunc — composable, testable, and zero-dependency on each other.
import ( "github.com/golang-jwt/jwt/v5" middleware "github.com/nelthaarion/breeze/middlewares" ) // Generate tokens accessToken, _ := middleware.GenerateJWT( "secret", jwt.MapClaims{"user_id": "abc", "role": "admin"}, 15*time.Minute, nil, ) refreshToken, _ := middleware.GenerateRefreshToken("refresh-secret", jwt.MapClaims{ "user_id": "abc", }, 7*24*time.Hour, nil) // Apply middleware router.Use(middleware.JWTAuthMiddleware(middleware.JWTOptions{ AccessSecret: "secret", RefreshSecret: "refresh-secret", SigningMethod: jwt.SigningMethodHS256, EnableRefreshToken: true, // auto-rotate expired access tokens RequiredRoles: []string{"admin"}, UserContextKey: "user", // stored in ctx.Param("user") ClaimsValidator: func(claims jwt.MapClaims) bool { return claims["active"] == true }, OnUnauthorized: func(ctx *breeze.Context, err error) { ctx.Status(401) ctx.JSON(map[string]string{"error": err.Error()}) }, }))
router.Use(middleware.CORSMiddleware(middleware.CORSOptions{ AllowOrigins: "https://myapp.com", AllowMethods: "GET,POST,PUT,DELETE,OPTIONS", AllowHeaders: "Content-Type,Authorization", AllowCredentials: "true", MaxAge: "86400", // preflight cache: 24h })) // OPTIONS preflight is handled automatically → 204 No Content
// 100 requests per minute per IP router.Use(middleware.NewRateLimiter(middleware.RateLimiterOptions{ Requests: 100, Per: time.Minute, Message: "Slow down — rate limit exceeded", })) // Different limits per route router.Handle(breeze.POST, "/login", loginHandler, middleware.NewRateLimiter(middleware.RateLimiterOptions{ Requests: 5, Per: time.Minute, }), ) // Returns 429 Too Many Requests when exceeded
// Automatically picks best encoding from Accept-Encoding header // Priority: br (Brotli) → gzip → deflate → none router.Use(middleware.CompressionMiddleware()) // Sets Content-Encoding response header automatically: // br → github.com/andybalholm/brotli // gzip → compress/gzip (stdlib) // deflate → compress/flate (stdlib)
// Safe defaults in one line router.Use(middleware.DefaultSecurityMiddleware()) // Or fully custom router.Use(middleware.SecurityMiddleware(middleware.SecurityOptions{ ContentSecurityPolicy: "default-src 'self'", XFrameOptions: "DENY", XContentTypeOptions: "nosniff", StrictTransportSecurity: "max-age=63072000; includeSubDomains; preload", ReferrerPolicy: "no-referrer", XXSSProtection: "1; mode=block", CrossOriginEmbedderPolicy: "require-corp", CrossOriginOpenerPolicy: "same-origin", CacheControl: "no-store, no-cache, must-revalidate", }))
// 1. Enable Swagger (before routes) router.Use(middleware.SwaggerMiddleware(router, middleware.SwaggerOptions{ Title: "My API", Version: "1.0.0", JSONPath: "/swagger.json", UIPath: "/swagger", })) // 2. Annotate routes with Go structs — no YAML, no codegen router.Handle(breeze.POST, "/users", createUser, middleware.DocPOST("/users", swagger.RouteDoc{ Title: "Create user", Tags: []string{"Users"}, Input: []swagger.InputGroup{ {Type: swagger.InputBody, Fields: CreateUserRequest{}, Required: true}, }, Output: UserResponse{}, OutputStatus: 201, }), ) // Visit /swagger → live Swagger UI // Visit /swagger.json → raw OpenAPI 3.1 JSON
// Register a WebSocket endpoint — returns the shared WSHub app := breeze.New(router, pool) hub := app.WebSocket("/ws", &breeze.WSHandlerFunc{ Connect: func(conn *breeze.WSConn) { conn.SendText("welcome") }, Message: func(conn *breeze.WSConn, opcode byte, payload []byte) { hub.BroadcastText(string(payload)) // echo to all clients }, Close: func(conn *breeze.WSConn, code uint16, reason string) { hub.BroadcastText("a user left") }, }) // Query active connections from any HTTP handler router.Handle(breeze.GET, "/ws/stats", func(ctx *breeze.Context) { ctx.JSON(map[string]int64{"connections": hub.Count()}) })
Requires Go 1.24.3 or later.
go get github.com/nelthaarion/breeze
The module pulls in gnet v2 for the event loop, go-json for fast JSON marshaling, brotli for compression, and golang-jwt/jwt for authentication. No transitive surprises.
A complete working server in under 20 lines.
package main import ( "runtime" "github.com/nelthaarion/breeze" middleware "github.com/nelthaarion/breeze/middlewares" ) func main() { router := breeze.NewRouter() router.Use(middleware.RecoveryMiddleware()) router.Use(middleware.LoggingMiddleware()) router.Handle(breeze.GET, "/", func(ctx *breeze.Context) { ctx.JSON(map[string]string{"status": "ok"}) }) router.Handle(breeze.GET, "/users/:id", func(ctx *breeze.Context) { ctx.JSON(map[string]string{"id": ctx.Param("id")}) }) pool := breeze.NewWorkerPool(runtime.NumCPU()) app := breeze.New(router, pool) app.Run(3000, true) // port, multiCore }
app.Run(port, multiCore) starts the gnet event loop with sensible defaults baked in.
| Option | Value | Effect |
|---|---|---|
| TCPNoDelay | enabled | Disables Nagle's algorithm — lower latency for small messages |
| Multicore | configurable | Spawns one event-loop per CPU core when true |
| LoadBalancing | RoundRobin | Distributes connections evenly across event loops |
app.Run(8080, true) // multicore — one loop per CPU, recommended app.Run(8080, false) // single-core — useful for dev / debugging
Create a router, register global middleware, then define routes. Routes support static segments, named parameters, and wildcards.
router := breeze.NewRouter() // Global middleware (runs on every route) router.Use(middleware.LoggingMiddleware()) // Static segment router.Handle(breeze.GET, "/health", healthHandler) // Named parameter — :id available via ctx.Param("id") router.Handle(breeze.GET, "/users/:id", getUser) router.Handle(breeze.POST, "/users", createUser) router.Handle(breeze.PUT, "/users/:id", updateUser) router.Handle(breeze.DELETE, "/users/:id", deleteUser) // Wildcard — *filepath captures everything after /files/ router.Handle(breeze.GET, "/files/*filepath", fileHandler) // Per-route middleware (runs only on this route) router.Handle(breeze.POST, "/admin/action", adminHandler, authMiddleware, auditMiddleware, )
Routes are matched in registration order. A static segment always beats a named parameter when both could match — register more specific routes first.
| Pattern | Matches | Params |
|---|---|---|
| /users | /users | — |
| /users/:id | /users/abc | id=abc |
| /users/:id/posts | /users/abc/posts | id=abc |
| /files/*path | /files/a/b/c.txt | path=a/b/c.txt |
Every handler receives a *breeze.Context. It carries the connection, parsed request, response, route params, and controls the middleware chain.
// JSON — sets Content-Type: application/json, status 200 ctx.JSON(map[string]any{"id": 1, "name": "Alice"}) // Plain text ctx.WriteString("Hello, World!") // HTML ctx.HTML([]byte("<h1>Hello</h1>")) // Override status code ctx.Status(201) // call after JSON/WriteString/HTML // Add/override a header ctx.SetHeader("X-Request-Id", "uuid-123")
// Advance to the next handler in the chain ctx.Next() // Short-circuit — skip all remaining handlers ctx.Abort() // Typical middleware pattern func AuthMiddleware(ctx *breeze.Context) { token := ctx.Req.Header["authorization"] if token == "" { ctx.Status(401) ctx.WriteString("Unauthorized") ctx.Abort() // ← stops the chain here return } ctx.Next() // ← continue to next handler }
ctx.Req is a *breeze.HTTPRequest parsed from raw bytes with zero unnecessary allocations.
// Method, Path, Headers method := ctx.Req.Method // breeze.Method ("GET", "POST", …) path := ctx.Req.Path // "/users/123" ct := ctx.Req.Header["content-type"] // headers are lowercased // Body var payload CreateUserRequest json.Unmarshal(ctx.Req.Body, &payload) // Query string page := ctx.Query("page") // "2" limit := ctx.Query("limit") // "20"
ctx.Res is a *breeze.HTTPResponse. Breeze serializes it to raw HTTP/1.1 bytes using strconv.AppendInt — no fmt.Sprintf, pre-sized buffer, array-indexed status text.
| Field | Type | Notes |
|---|---|---|
| Status | int | HTTP status code (200, 404, …) |
| Headers | map[string]string | Copy-on-write — safe to mutate via SetHeader |
| Body | []byte | Raw response body |
You rarely set ctx.Res directly — use the helpers (JSON, WriteString, HTML, Status, SetHeader) instead.
// Route: /users/:id/posts/:postId userID := ctx.Param("id") // "abc" postID := ctx.Param("postId") // "42" // Query: /search?q=go&page=2 q := ctx.Query("q") // "go" page := ctx.Query("page") // "2" // Middleware can pass data downstream via params ctx.SetParam("userID", "from-auth") // in handler: uid := ctx.GetParam("userID") // "from-auth" // Get a snapshot of all params all := ctx.GetParams() // map[string]string (copy)
Logs method, path, status, and elapsed time for every request. Output goes to stdout in RFC3339 format.
router.Use(middleware.LoggingMiddleware()) // Output: [Breeze][2026-06-23T12:00:00Z] GET /users -> 200 (1.2ms)
Wraps the entire handler chain in a defer/recover. On panic: logs the value + full stack trace, sets 500, calls ctx.Abort(). Your server never crashes from a handler bug.
router.Use(middleware.RecoveryMiddleware()) // Register first so it wraps everything else
Sets Access-Control-* headers and handles OPTIONS preflight automatically.
router.Use(middleware.CORSMiddleware(middleware.CORSOptions{ AllowOrigins: "*", AllowMethods: "GET,POST,PUT,DELETE", AllowHeaders: "Content-Type,Authorization", ExposeHeaders: "X-Request-Id", AllowCredentials: "true", MaxAge: "86400", }))
Sets 12 security-hardening HTTP headers. Use the opinionated defaults or configure each header individually.
// One-liner safe defaults router.Use(middleware.DefaultSecurityMiddleware()) // Custom router.Use(middleware.SecurityMiddleware(middleware.SecurityOptions{ ContentSecurityPolicy: "default-src 'self'; img-src *", XFrameOptions: "SAMEORIGIN", StrictTransportSecurity: "max-age=31536000", }))
| Header | Default |
|---|---|
| Content-Security-Policy | default-src 'self' |
| X-Frame-Options | DENY |
| X-Content-Type-Options | nosniff |
| Strict-Transport-Security | max-age=63072000; includeSubDomains; preload |
| X-XSS-Protection | 1; mode=block |
| Referrer-Policy | no-referrer |
| Cache-Control | no-store, no-cache, must-revalidate |
Full JWT authentication with optional refresh token rotation, RBAC, and custom claims validation. Tokens are looked up from the Authorization: Bearer header by default.
// Generate token, _ := middleware.GenerateJWT("secret", jwt.MapClaims{ "user_id": "u-123", "role": "admin", }, 15*time.Minute, nil) refresh, _ := middleware.GenerateRefreshToken("refresh-secret", jwt.MapClaims{"user_id": "u-123"}, 7*24*time.Hour, nil) // Validate router.Use(middleware.JWTAuthMiddleware(middleware.JWTOptions{ AccessSecret: "secret", RefreshSecret: "refresh-secret", EnableRefreshToken: true, // new token returned in X-New-Access-Token RequiredRoles: []string{"admin", "editor"}, UserContextKey: "user", }))
// Read from cookie instead of header TokenLookup: func(ctx *breeze.Context) (string, string, error) { access := ctx.Req.Header["x-access-token"] refresh := ctx.Req.Header["x-refresh-token"] if access == "" { return "", "", fmt.Errorf("token missing") } return access, refresh, nil },
In-memory token counter per remote IP. Resets after each Per window. Returns 429 Too Many Requests with a configurable message.
// Global limit router.Use(middleware.NewRateLimiter(middleware.RateLimiterOptions{ Requests: 100, Per: time.Minute, })) // Strict limit on a specific route router.Handle(breeze.POST, "/login", loginHandler, middleware.NewRateLimiter(middleware.RateLimiterOptions{ Requests: 5, Per: time.Minute, Message: "Too many login attempts", }), )
Computes an MD5 ETag from the response body. On subsequent requests with a matching If-None-Match, returns 304 Not Modified with an empty body — saving bandwidth.
cache := middleware.NewETagCache() router.Use(cache.ETagMiddleware()) // Client flow: // → GET /data // ← 200 ETag: "abc123" // → GET /data If-None-Match: "abc123" // ← 304 Not Modified (empty body)
Negotiates the best encoding from the client's Accept-Encoding header. Priority: Brotli → Gzip → Deflate → none.
router.Use(middleware.CompressionMiddleware()) // Sets Content-Encoding: br / gzip / deflate automatically
Annotate routes at registration time using Go structs. Breeze reflects the types to produce a live OpenAPI 3.1 spec — no YAML, no code generation, no build step.
// Types — use json + description + example struct tags type CreateUserRequest struct { Name string `json:"name" description:"Full name" example:"Alice"` Email string `json:"email" description:"Email address" example:"alice@example.com"` Age int `json:"age" description:"Age in years" example:"30"` } type UserResponse struct { ID string `json:"id"` Name string `json:"name"` Email string `json:"email"` } // Enable once before routes router.Use(middleware.SwaggerMiddleware(router, middleware.SwaggerOptions{ Title: "Users API", Version: "2.0.0", JSONPath: "/swagger.json", UIPath: "/swagger", })) // Annotate each route router.Handle(breeze.POST, "/users", createUser, middleware.DocPOST("/users", swagger.RouteDoc{ Title: "Create user", Tags: []string{"Users"}, Input: []swagger.InputGroup{ {Type: swagger.InputBody, Fields: CreateUserRequest{}, Required: true}, }, Output: UserResponse{}, OutputStatus: 201, }), ) router.Handle(breeze.GET, "/users/:id", getUser, middleware.DocGET("/users/:id", swagger.RouteDoc{ Title: "Get user", Tags: []string{"Users"}, Input: []swagger.InputGroup{ {Type: swagger.InputParams, Fields: struct{ ID string `json:"id"` }{}}, {Type: swagger.InputQuery, Fields: struct{ Fields string `json:"fields,omitempty"` }{}}, }, Output: UserResponse{}, }), )
| Constant | Source | OpenAPI Location |
|---|---|---|
| swagger.InputBody | JSON request body | requestBody |
| swagger.InputQuery | URL query params | query |
| swagger.InputParams | Path parameters | path |
| swagger.InputHeader | Request headers | header |
Breeze implements RFC 6455 WebSocket directly inside the gnet event loop — no goroutine-per-connection, no third-party WS library. After the HTTP upgrade handshake, the connection is promoted to WebSocket mode. All subsequent traffic for that file descriptor bypasses the HTTP parser entirely and is handled by the WebSocket frame engine.
WebSocket routes are registered on the *Breeze app (not the router) using app.WebSocket(), which returns the shared *WSHub immediately. The hub can be injected into your handler at construction time and used from any goroutine.
package main import ( "fmt" "runtime" "github.com/nelthaarion/breeze" ) // ── Struct-based handler ───────────────────────────────────────────── type ChatHandler struct { hub *breeze.WSHub } func (h *ChatHandler) OnConnect(conn *breeze.WSConn) { fmt.Printf("[ws] connected: %s (total: %d)\n", conn.RemoteAddr(), h.hub.Count()) h.hub.BroadcastExcept(breeze.WsOpText, []byte("a user joined"), conn) } func (h *ChatHandler) OnMessage(conn *breeze.WSConn, opcode byte, payload []byte) { if opcode == breeze.WsOpText { msg := fmt.Sprintf("[%s]: %s", conn.RemoteAddr(), string(payload)) h.hub.BroadcastText(msg) } else { conn.SendBinary(payload) // echo binary back } } func (h *ChatHandler) OnClose(conn *breeze.WSConn, code uint16, reason string) { h.hub.BroadcastText("a user left") } func main() { router := breeze.NewRouter() pool := breeze.NewWorkerPool(runtime.NumCPU()) app := breeze.New(router, pool) // WebSocket() registers the route and returns the hub immediately. chat := &ChatHandler{} chat.hub = app.WebSocket("/ws", chat) app.Run(3000, true) }
On every incoming data event, Breeze checks a sync.Map (O(1), lock-free) to see if the file descriptor belongs to a promoted WebSocket connection. If it does, the HTTP parser is never invoked — the data goes straight to the WebSocket frame engine. This means WebSocket connections carry zero HTTP overhead after the upgrade and HTTP connections carry zero WebSocket overhead.
Implement WSHandler on a struct, or use the inline WSHandlerFunc shorthand. Fields left nil in WSHandlerFunc are silently skipped.
// Full interface — implement on your own struct type WSHandler interface { OnConnect(conn *WSConn) OnMessage(conn *WSConn, opcode byte, payload []byte) OnClose(conn *WSConn, code uint16, reason string) } // Inline shorthand — nil fields are no-ops app.WebSocket("/ws/echo", &breeze.WSHandlerFunc{ Connect: func(conn *breeze.WSConn) { conn.SendText("echo server ready") }, Message: func(conn *breeze.WSConn, opcode byte, payload []byte) { conn.Send(opcode, payload) // reflect back verbatim }, // Close field omitted — no-op on disconnect })
| Constant | Value | Use |
|---|---|---|
| breeze.WsOpText | 0x1 | UTF-8 text message |
| breeze.WsOpBinary | 0x2 | Binary message (any payload) |
Ping, Pong, and Close opcodes are handled automatically by the engine and are never surfaced to OnMessage.
*WSConn is the per-client handle passed to every callback. It wraps the underlying gnet.Conn with WebSocket framing and is safe to call from any goroutine.
// Send a text message conn.SendText("hello") // Send binary data conn.SendBinary([]byte{0x01, 0x02}) // Send with explicit opcode conn.Send(breeze.WsOpText, []byte("raw")) // Graceful close — sends a Close frame, then closes the TCP connection // Code 1000 = Normal Closure, 1001 = Going Away, 1008 = Policy Violation, … conn.Close(1000, "bye") // Remote address of the client addr := conn.RemoteAddr() // e.g. "192.168.1.5:54321"
| Method | Signature | Notes |
|---|---|---|
| Send | (opcode byte, payload []byte) error | Low-level send with explicit opcode |
| SendText | (msg string) error | Convenience wrapper for text frames |
| SendBinary | (msg []byte) error | Convenience wrapper for binary frames |
| Close | (code uint16, reason string) | Sends Close frame; idempotent |
| RemoteAddr | () string | Client IP:port string |
Breeze handles RFC 6455 fragmentation transparently. Continuation frames are accumulated in a per-connection buffer and OnMessage is only called once the final frame (FIN=1) arrives. The default maximum payload size per message is 4 MiB. Protocol errors or oversized payloads trigger an automatic Close with code 1002 or 1009.
*WSHub is the shared broadcast hub — one instance per *Breeze app, regardless of how many WebSocket routes are registered. It is safe to call from any goroutine. Broadcast builds the WebSocket frame once and delivers it to all clients concurrently via the worker pool.
// Retrieve the hub from the app at any point after WebSocket() is called hub := app.Hub() // returns nil if no WS routes registered // Broadcast text to every connected client hub.BroadcastText("server restarting in 10s") // Broadcast binary to every connected client hub.BroadcastBinary(protobufPayload) // Broadcast with explicit opcode hub.Broadcast(breeze.WsOpText, []byte("raw")) // Broadcast to all clients except one (e.g. exclude the sender) hub.BroadcastExcept(breeze.WsOpText, []byte(msg), senderConn) // Active connection count (atomic read — no lock) n := hub.Count() // int64 // Expose connection count via HTTP router.Handle(breeze.GET, "/ws/stats", func(ctx *breeze.Context) { ctx.JSON(map[string]int64{"connections": hub.Count()}) })
| Method | Signature | Notes |
|---|---|---|
| Broadcast | (opcode byte, payload []byte) | Frame built once, delivered to all |
| BroadcastText | (msg string) | Convenience wrapper |
| BroadcastBinary | (msg []byte) | Convenience wrapper |
| BroadcastExcept | (opcode byte, payload []byte, skip *WSConn) | Skips one connection |
| Count | () int64 | Atomic, no lock |
All routes registered with app.WebSocket() share the same hub. BroadcastText reaches clients on all WS paths. If you need isolated rooms, implement per-room filtering in OnMessage using BroadcastExcept or maintain your own client sets.
// Multiple endpoints — same hub is returned each time hub1 := app.WebSocket("/ws/chat", chatHandler) hub2 := app.WebSocket("/ws/events", eventsHandler) // hub1 == hub2 == app.Hub()
Breeze sends and receives standard RFC 6455 close codes. Your OnClose callback receives the code and reason string extracted from the Close frame. If the TCP connection drops without a Close frame (network loss, RST), Breeze synthesises code 1006 with reason "abnormal closure".
| Code | Name | When |
|---|---|---|
| 1000 | Normal Closure | Clean close initiated by either side |
| 1001 | Going Away | Server shutting down or client navigating away |
| 1002 | Protocol Error | Malformed frame — Breeze closes automatically |
| 1003 | Unsupported Data | Unknown opcode — Breeze closes automatically |
| 1006 | Abnormal Closure | TCP disconnect without Close frame (synthesised) |
| 1008 | Policy Violation | Application-level rejection (use in your handler) |
| 1009 | Message Too Big | Payload exceeds wsMaxPayloadDefault (4 MiB) |
| 1011 | Internal Error | Unexpected server-side condition |
// Reject unauthenticated connections after upgrade func (h *SecureHandler) OnConnect(conn *breeze.WSConn) { if !isAuthenticated(conn) { conn.Close(1008, "policy violation: not authenticated") return } conn.SendText("authenticated") } // Inspect why a connection closed func (h *SecureHandler) OnClose(conn *breeze.WSConn, code uint16, reason string) { switch code { case 1000: fmt.Println("clean disconnect") case 1006: fmt.Println("connection dropped unexpectedly") default: fmt.Printf("closed code=%d reason=%q\n", code, reason) } }
Parse multipart forms and save files to disk — no dependency on http.Request.
// Parse all parts at once files, fields, err := ctx.ParseMultipart(10 << 20) // 10 MB max per file if err != nil { ctx.Status(400); ctx.WriteString(err.Error()); return } uf := files["avatar"][0] fmt.Println(uf.Filename, uf.ContentType, uf.Size) // Or save directly to disk saved, err := ctx.SaveUploadedFile("avatar", "./uploads/avatar.jpg", 5<<20) if err != nil { ctx.Status(400); ctx.WriteString(err.Error()); return } ctx.JSON(map[string]string{"saved": saved})
| Field | Type | Description |
|---|---|---|
| Field | string | Form field name |
| Filename | string | Original filename from the browser |
| ContentType | string | From Content-Type header or auto-sniffed |
| Size | int64 | Size in bytes |
| Content | []byte | Raw file bytes |
// Serve ./public/* at /assets/* router.ServeStatic("/assets", "./public") // Auto-serve ./public/index.html at GET / (no explicit route needed) // Directory traversal is sanitized with filepath.Clean // MIME types resolved by extension, fallback to content sniffing
Off-loads handler execution from the gnet event loop so slow handlers don't block all connections on a reactor.
// Size to NumCPU (good for I/O-bound handlers) pool := breeze.NewWorkerPool(runtime.NumCPU()) // Custom concurrency pool := breeze.NewWorkerPool(32) // Graceful shutdown (wait up to 5s for in-flight tasks) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() pool.Shutdown(ctx) // Pass nil to skip the pool and use raw goroutines app := breeze.New(router, nil)
| Scenario | Behaviour |
|---|---|
| Queue has capacity | Task enqueued normally (channel buffer = workers × 16) |
| Queue full (burst) | Falls back to go task() — event loop is never blocked |
| pool is nil | Uses go exec() per request |