Open-Source Wikis

/

nginx

/

Primitives

/

Cycle (`ngx_cycle_t`)

nginx/nginx

Cycle (ngx_cycle_t)

Active contributors: Maxim Dounin, Sergey Kandaurov

What it is

The ngx_cycle_t is nginx's "world." It owns the parsed configuration, the listening sockets, the connection table, the module list, the open file table, and the shared-memory zones. Almost every long-lived state pointer in the codebase ultimately roots back to a cycle.

There is one global ngx_cycle (a volatile ngx_cycle_t *) that everyone reads. On a SIGHUP reload, a new cycle is built, the global pointer is swapped, and the old cycle keeps running until its workers drain — that's the basis of the zero-downtime reload model.

Definition

src/core/ngx_cycle.h:

struct ngx_cycle_s {
    void                  ****conf_ctx;            /* per-module config trees */
    ngx_pool_t               *pool;                /* allocations live here */

    ngx_log_t                *log;                 /* the cycle's logger */
    ngx_log_t                 new_log;             /* in-progress log during reload */

    ngx_uint_t                log_use_stderr;

    ngx_connection_t        **files;               /* fd -> connection */
    ngx_connection_t         *free_connections;    /* per-worker free list */
    ngx_uint_t                free_connection_n;

    ngx_module_t            **modules;
    ngx_uint_t                modules_n;
    ngx_uint_t                modules_used;

    ngx_queue_t               reusable_connections_queue;
    ngx_uint_t                reusable_connections_n;
    time_t                    connections_reuse_time;

    ngx_array_t               listening;           /* ngx_listening_t[] */
    ngx_array_t               paths;               /* ngx_path_t *[] */

    ngx_array_t               config_dump;
    ngx_rbtree_t              config_dump_rbtree;
    ngx_rbtree_node_t         config_dump_sentinel;

    ngx_list_t                open_files;          /* ngx_open_file_t */
    ngx_list_t                shared_memory;       /* ngx_shm_zone_t */

    ngx_uint_t                connection_n;
    ngx_uint_t                files_n;

    ngx_connection_t         *connections;
    ngx_event_t              *read_events;
    ngx_event_t              *write_events;

    ngx_cycle_t              *old_cycle;

    ngx_str_t                 conf_file;
    ngx_str_t                 conf_param;
    ngx_str_t                 conf_prefix;
    ngx_str_t                 prefix;
    ngx_str_t                 error_log;
    ngx_str_t                 lock_file;
    ngx_str_t                 hostname;
};

Lifecycle

Created by ngx_init_cycle(old_cycle) (src/core/ngx_cycle.c):

  1. Allocate a new ngx_pool_t — everything below comes out of it.
  2. Allocate the cycle struct from that pool.
  3. Copy paths, prefix, conf_file from old_cycle (or the CLI args on first init).
  4. For each module, call create_conf (CORE modules) to build empty config structs.
  5. Parse nginx.conf into the new cycle (via ngx_conf_parse).
  6. For each module, call init_conf (defaults + validation).
  7. Open the shared zones; reattach to old shm where the size and tag match.
  8. Open the listening sockets — reusing fds from old_cycle->listening where possible.
  9. Allocate connection table (connections, read_events, write_events).
  10. For each module, call init_module(cycle).
  11. Open log files.
  12. Return the new cycle.

If anything fails, the function returns NULL and the old cycle continues to run. This is the invariant nginx leans on: the old cycle is preserved, the old workers are still serving, and the operator can just edit the config and try again.

Old cycle handoff

After ngx_init_cycle returns successfully, the master:

  1. Sets the new global ngx_cycle = new_cycle.
  2. Forks new workers against the new cycle.
  3. Sends NGX_CMD_QUIT to old workers; they finish in-flight requests and exit.
  4. Eventually destroys the old cycle (ngx_destroy_cycle() — clears old_cycle->pool).

Listening sockets stay open across this transition: the new workers inherit them via cycle->listening carrying over the same fds.

Working with cycles

Most code never creates or destroys a cycle. It just reads ngx_cycle->conf_ctx, allocates from ngx_cycle->pool, or registers callbacks via the cycle's facilities (open file, shared memory).

Common idioms:

ngx_cycle_t  *cycle = ngx_cycle;          /* read the global */
void         *core_conf = ngx_get_conf(cycle->conf_ctx, ngx_core_module);

ngx_pool_t   *pool = cycle->pool;          /* short-lived allocations only at startup */
ngx_log_t    *log  = cycle->log;

In HTTP modules, the per-module config is pulled out via ngx_http_cycle_get_module_main_conf.

Pitfalls

  • Don't store pointers across cycle boundaries. A pointer into the old cycle's pool becomes a use-after-free after ngx_destroy_cycle. Modules that need long-lived state across reloads use shared memory zones, not the cycle pool.
  • init_module can be called multiple times — once on initial startup, once on each successful reload. Make it idempotent and cheap.
  • cycle->listening is array of ngx_listening_t, not pointers. Index, don't pointer-traverse.

Cross-references

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Cycle (`ngx_cycle_t`) – nginx wiki | Factory