Skip to content

Add Async Handlers Support#112

Open
g41797 wants to merge 26 commits into
laytan:mainfrom
g41797:main
Open

Add Async Handlers Support#112
g41797 wants to merge 26 commits into
laytan:mainfrom
g41797:main

Conversation

@g41797
Copy link
Copy Markdown

@g41797 g41797 commented May 1, 2026

Async handlers ("split" handlers) are based on the already existing async body reading flow

Split Handler Pattern

One handler proc, called twice:

handle :: proc(h: ^http.Handler, req: ^http.Request, res: ^http.Response) {
    if res.work_data == nil { // first call runs inside nbio.tick()
        work := new(My_Work, alloc)
        http.mark_async(h, res, work)
        // start background work .....
        return
    }

    // Second call - after exit from tick()
    work := (^My_Work)(res.work_data)
    defer {
        // free work struct
        res.work_data = nil
    }
    http.respond_plain(res, work.result)
}

res.work_data - shared data between two calls

Flow (includes body reading)

        IO thread                          Background thread
─────────────────────────────────────   ─────────────────────────
handler (first call)
  res.async_handler = h
  http.body(req, -1, res, body_callback)
  return

body_callback (IO thread)
  mark_async(res.async_handler, res, work)
  thread.start(background_proc) ----------------
  return                                       |
                                               V
                                        background processing
                                        result => work
                                        resume(res):
        [nbio] <---------------------(2)
          |        [mpsc queue]<-----(1)
          V             |
[after tick() returns]  |
                        V
              handler (second call)
                       respond

"Async" Changes

New:

  • resume.odin — public API: mark_async, cancel_async, resume
  • internal/mpsc/ — lock-free MPSC queue, used for resumed handlers

Modified:

  • response.odin — async fields:
Response :: struct {
	// ------ async "header"
	using node:       list.Node,
	async_handler:    ^Handler, // handler to resume
	work_data:        rawptr, // non-nil means async is pending
	// ------
  • server.odin

    • resume loop after nbio.tick() {gets handler from queue + runs it}
    • shutdown waits for pending async handlers (max 5s)

Non-Async Changes

conn_handle_req does nothing when closing is set, it allows safe close and prevents SIGBUS (macOS) and memory leaks (Windows):

conn_handle_req :: proc(c: ^Connection, ...) {
    if atomic_load(&c.server.closing) {return}
    ...
}

Sometimes the server hung during shutdown — added 1ms timeout to nbio.tick().

Examples

examples/async/ — three examples

All use thread.create per request (for illustration only)

Production code should use a worker pool or pipeline

Known problems

Docs generation on GitHub failed - Odin version dev-2026-04-nightly:0c9ea9b

Run ./docs/generate.sh
+ cd docs
+ rm -rf build
+ mkdir build
+ odin doc . -all-packages -doc-format
./docs/generate.sh: line 10:  2496 Segmentation fault      (core dumped) odin doc . -all-packages -doc-format
Error: Process completed with exit code 139

Local generation - ok - Odin version dev-2026-03-nightly

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.

1 participant