Skip to content

Waiting without blocking

A worker of nginx serves many connections at once from one event loop. A handler that waits in a blocking call, such as sleep or a socket read, stops the whole worker. Nginx::Async.sleep and Nginx::Async::HTTP.sub_request wait through nginx instead: the Ruby code of the handler runs in a fiber, which yields to nginx and is resumed from nginx’s event loop.

What happens when an access handler calls Nginx::Async.sleep, from the call until the handler's code goes on.Drawn from these lines of ngx_mruby at 0c72e3970 (2026-10-19):
  • mrbgems/ngx_mruby_mrblib/mrblib/mrb_nginx.rb lines 100 to 106
  • src/http/ngx_http_mruby_async.c lines 187 to 239, 241 to 314, 316 to 348 and 363 to 410
  • src/http/ngx_http_mruby_module.c lines 1061 to 1074
  • src/http/ngx_http_mruby_core.h lines 30 to 35
  • ngx_mruby runs the code of each handler in a fiber of mruby (ngx_mrb_start_fiber). Nginx::Async.sleep arms an nginx timer and yields the fiber; while it is suspended, the handler has returned NGX_DONE, and the worker serves other connections.
  • When the timer fires, ngx_mrb_timer_handler resumes the fiber with the request that it belongs to. If the code ends, ngx_mruby gives nginx the result of the handler: the next handler of the phase runs for Nginx::DECLINED, and nginx ends the request with any other result.
  • Nginx::Async::HTTP.sub_request works in the same way: it starts an nginx subrequest and yields, and nginx resumes the fiber when the subrequest has finished.
  • mruby_set and the post-read, server rewrite, rewrite and access handlers can wait and resume. A suspended handler returns NGX_DONE, or NGX_AGAIN while a subrequest continues.
  • Since the 2.7.3 changes, also integrated into 3.x, nginx keeps a normal content handler’s request while Nginx::Async.sleep waits. The timer resumes the fiber; its response or returned status completes the request. Up to released 2.7.2, the request ended at the sleep without a response. A content handler reached by Nginx.redirect from Ruby still has that limitation: the caller continues and can answer the request itself.
  • A content handler cannot call Nginx::Async::HTTP.sub_request after it has waited in Nginx::Async.sleep; this raises RuntimeError. In mruby_set, a subrequest does not hold the request: nginx continues to the content phase without waiting for the variable. In header/body filters and the log handler, both async methods raise RuntimeError. See the 2.7.3 behavior changes and Nginx::Async#sleep.
  • Code that waits in sleep of mruby-sleep, a socket of mruby-socket, a file, a shell command or a Redis call blocks the worker for that time (Trust boundary).

Nginx::Async in the Ruby reference describes both methods.