Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 54 additions & 7 deletions src/nxt_application.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ static void nxt_proto_start_process_handler(nxt_task_t *task,
static void nxt_proto_quit_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg);
static void nxt_proto_process_created_handler(nxt_task_t *task,
nxt_port_recv_msg_t *msg);
static void nxt_proto_quit_children(nxt_task_t *task);
static void nxt_proto_quit_children(nxt_task_t *task, uint8_t quit_param);
static nxt_process_t *nxt_proto_process_find(nxt_task_t *task, nxt_pid_t pid);
static void nxt_proto_process_add(nxt_task_t *task, nxt_process_t *process);
static nxt_process_t *nxt_proto_process_remove(nxt_task_t *task, nxt_pid_t pid);
Expand Down Expand Up @@ -680,9 +680,36 @@ nxt_proto_start_process_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
static void
nxt_proto_quit_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
{
nxt_debug(task, "prototype quit handler");
uint8_t quit_param;

nxt_proto_quit_children(task);
/*
* The QUIT message from main carries a single nxt_port_quit_mode_t
* byte (see nxt_runtime_quit_buf() in src/nxt_runtime.c). Forward
* it to the children unchanged so SIGQUIT to main results in
* NXT_PORT_QUIT_GRACEFUL reaching every libunit context, not just
* the children main contacts directly. Empty body (legacy senders,
* or main's NORMAL fast-exit path which omits the payload) is
* treated as NXT_PORT_QUIT_NORMAL.
*
* Unknown payload values (anything other than 0 or 1) are also
* normalised to NORMAL: internal senders should only emit the two
* defined nxt_port_quit_mode_t values, but a malformed or
* future-incompatible sender must not be allowed to propagate a
* bogus byte through the whole worker pool.
*/
quit_param = NXT_PORT_QUIT_NORMAL;

if (msg->buf != NULL && nxt_buf_mem_used_size(&msg->buf->mem) >= 1) {
quit_param = msg->buf->mem.pos[0];

if (quit_param != NXT_PORT_QUIT_GRACEFUL) {
quit_param = NXT_PORT_QUIT_NORMAL;
}
}

nxt_debug(task, "prototype quit handler (quit_param=%d)", quit_param);

nxt_proto_quit_children(task, quit_param);

nxt_proto_exiting = 1;

Expand All @@ -693,16 +720,30 @@ nxt_proto_quit_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)


static void
nxt_proto_quit_children(nxt_task_t *task)
nxt_proto_quit_children(nxt_task_t *task, uint8_t quit_param)
{
nxt_buf_t *b;
nxt_port_t *port;
nxt_process_t *process;

nxt_queue_each(process, &nxt_proto_children, nxt_process_t, link) {
port = nxt_process_port_first(process);

(void) nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT,
-1, 0, 0, NULL);
b = nxt_runtime_quit_buf(task, quit_param);

if (nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT,
-1, 0, 0, b)
!= NXT_OK
&& b != NULL)
{
/*
* Port layer never took ownership of the GRACEFUL payload
* buffer; release it explicitly so the cascaded byte does
* not leak. Mirrors the cleanup in
* src/nxt_runtime.c nxt_runtime_stop_*_processes().
*/
b->completion_handler(task, b, b->parent);
}
}
nxt_queue_loop;
}
Expand Down Expand Up @@ -759,7 +800,13 @@ nxt_proto_sigterm_handler(nxt_task_t *task, void *obj, void *data)
nxt_trace(task, "signal signo:%d (%s) received",
(int) (uintptr_t) obj, data);

nxt_proto_quit_children(task);
/*
* Direct signal to the prototype process is not the user-initiated
* lifecycle path (that goes main -> NXT_PORT_MSG_QUIT -> the message
* handler above). Treat it as fast exit so children drop in flight
* rather than wait on a graceful drain that nobody requested.
*/
nxt_proto_quit_children(task, NXT_PORT_QUIT_NORMAL);

nxt_proto_exiting = 1;

Expand Down
22 changes: 20 additions & 2 deletions src/nxt_main_process.c
Original file line number Diff line number Diff line change
Expand Up @@ -835,10 +835,19 @@ nxt_main_process_title(nxt_task_t *task)
static void
nxt_main_process_sigterm_handler(nxt_task_t *task, void *obj, void *data)
{
nxt_runtime_t *rt;

nxt_debug(task, "sigterm handler signo:%d (%s)",
(int) (uintptr_t) obj, data);

/* TODO: fast exit. */
rt = task->thread->runtime;

/*
* Fast exit: do not drain in-flight requests. The QUIT byte sent
* to libunit workers (see nxt_runtime_stop_app_processes()) carries
* NXT_PORT_QUIT_NORMAL so nxt_unit_quit() returns immediately.
*/
rt->quit_mode = NXT_PORT_QUIT_NORMAL;

nxt_exiting = 1;

Expand All @@ -849,10 +858,19 @@ nxt_main_process_sigterm_handler(nxt_task_t *task, void *obj, void *data)
static void
nxt_main_process_sigquit_handler(nxt_task_t *task, void *obj, void *data)
{
nxt_runtime_t *rt;

nxt_debug(task, "sigquit handler signo:%d (%s)",
(int) (uintptr_t) obj, data);

/* TODO: graceful exit. */
rt = task->thread->runtime;

/*
* Graceful exit: ask libunit workers to drain in-flight requests
* before tearing the per-context state down (see nxt_unit_quit() at
* src/nxt_unit.c:5753).
*/
rt->quit_mode = NXT_PORT_QUIT_GRACEFUL;

nxt_exiting = 1;

Expand Down
12 changes: 12 additions & 0 deletions src/nxt_port.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,18 @@ typedef enum {
} nxt_port_msg_type_t;


/*
* Wire-format payload for NXT_PORT_MSG_QUIT. A single byte selects
* between fast and graceful exit on the receiving side. When the
* message arrives without a payload, the receiver defaults to
* NXT_PORT_QUIT_NORMAL (see src/nxt_unit.c nxt_unit_process_msg).
*/
typedef enum {
NXT_PORT_QUIT_NORMAL = 0,
NXT_PORT_QUIT_GRACEFUL = 1,
} nxt_port_quit_mode_t;


/* Passed as a first iov chunk. */
typedef struct {
uint32_t stream;
Expand Down
67 changes: 63 additions & 4 deletions src/nxt_runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -491,9 +491,46 @@ nxt_runtime_close_idle_connections(nxt_event_engine_t *engine)
}


/*
* Allocate a one-byte port-message body carrying a nxt_port_quit_mode_t
* byte for NXT_PORT_MSG_QUIT. libunit and the prototype handler both
* parse this byte and fall back to NXT_PORT_QUIT_NORMAL when the
* message arrives without a payload, so the fast-exit path needs no
* payload at all: callers receive NULL and pass it straight to
* nxt_port_socket_write().
*
* Allocation failure under GRACEFUL intentionally degrades to the
* NORMAL fast exit -- a sane behaviour under memory pressure -- but
* the degradation is logged so the operator knows a SIGQUIT did not
* actually drain in-flight requests on at least one cascade leg.
*/
nxt_buf_t *
nxt_runtime_quit_buf(nxt_task_t *task, uint8_t quit_param)
{
nxt_buf_t *b;

if (quit_param == NXT_PORT_QUIT_NORMAL) {
return NULL;
}

b = nxt_buf_mem_alloc(task->thread->engine->mem_pool, 1, 0);
Comment thread
andypost marked this conversation as resolved.
if (nxt_slow_path(b == NULL)) {
nxt_log(task, NXT_LOG_WARN,
"graceful quit payload allocation failed; "
"this cascade leg falls back to fast exit");
return NULL;
}

*b->mem.free++ = quit_param;

return b;
}
Comment thread
andypost marked this conversation as resolved.


void
nxt_runtime_stop_app_processes(nxt_task_t *task, nxt_runtime_t *rt)
{
nxt_buf_t *b;
nxt_port_t *port;
nxt_process_t *process;
nxt_process_init_t *init;
Expand All @@ -508,8 +545,21 @@ nxt_runtime_stop_app_processes(nxt_task_t *task, nxt_runtime_t *rt)

nxt_process_port_each(process, port) {

(void) nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, -1,
0, 0, NULL);
b = nxt_runtime_quit_buf(task, rt->quit_mode);

if (nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, -1,
0, 0, b)
!= NXT_OK
&& b != NULL)
{
/*
* Port layer never took ownership of the GRACEFUL
* payload buffer; release it explicitly so it does
* not leak from the engine memory pool. Same shape
* as PR #8 (port IPC completion leaks).
*/
b->completion_handler(task, b, b->parent);
}

} nxt_process_port_loop;
}
Expand All @@ -521,6 +571,7 @@ nxt_runtime_stop_app_processes(nxt_task_t *task, nxt_runtime_t *rt)
static void
nxt_runtime_stop_all_processes(nxt_task_t *task, nxt_runtime_t *rt)
{
nxt_buf_t *b;
nxt_port_t *port;
nxt_process_t *process;

Expand All @@ -530,8 +581,16 @@ nxt_runtime_stop_all_processes(nxt_task_t *task, nxt_runtime_t *rt)

nxt_debug(task, "%d sending quit to %PI", rt->type, port->pid);

(void) nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, -1, 0,
0, NULL);
b = nxt_runtime_quit_buf(task, rt->quit_mode);

if (nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, -1, 0,
0, b)
!= NXT_OK
&& b != NULL)
{
/* Same cleanup as nxt_runtime_stop_app_processes() above. */
b->completion_handler(task, b, b->parent);
}

} nxt_process_port_loop;

Expand Down
10 changes: 10 additions & 0 deletions src/nxt_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ struct nxt_runtime_s {
uint8_t batch;
uint8_t status;
uint8_t is_pid_isolated;
/* See nxt_port_quit_mode_t in nxt_port.h. */
uint8_t quit_mode;

const char *engine;
uint32_t engine_connections;
Expand Down Expand Up @@ -119,6 +121,14 @@ nxt_port_t *nxt_runtime_process_port_create(nxt_task_t *task, nxt_runtime_t *rt,
void nxt_runtime_port_remove(nxt_task_t *task, nxt_port_t *port);
void nxt_runtime_stop_app_processes(nxt_task_t *task, nxt_runtime_t *rt);

/*
* Allocate a NXT_PORT_MSG_QUIT body byte carrying quit_param. Returns
* NULL when quit_param == NXT_PORT_QUIT_NORMAL (no allocation; libunit
* defaults to NORMAL when the QUIT message arrives without a payload)
* or when allocation fails (degrades to NORMAL under memory pressure).
*/
nxt_buf_t *nxt_runtime_quit_buf(nxt_task_t *task, uint8_t quit_param);

NXT_EXPORT nxt_port_t *nxt_runtime_port_find(nxt_runtime_t *rt, nxt_pid_t pid,
nxt_port_id_t port_id);

Expand Down
14 changes: 10 additions & 4 deletions src/nxt_unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@
#define NXT_UNIT_LOCAL_BUF_SIZE \
(NXT_UNIT_MAX_PLAIN_SIZE + sizeof(nxt_port_msg_t))

enum {
NXT_QUIT_NORMAL = 0,
NXT_QUIT_GRACEFUL = 1,
};
/*
* Wire-protocol QUIT mode selector. The canonical enum lives in
* src/nxt_port.h alongside NXT_PORT_MSG_QUIT itself; the aliases
* below preserve the original local names without risking divergence
* from the daemon-side usage (the preprocessor substitutes the same
* enum value into every reference, so a compile-time mismatch is
* impossible).
*/
#define NXT_QUIT_NORMAL NXT_PORT_QUIT_NORMAL
#define NXT_QUIT_GRACEFUL NXT_PORT_QUIT_GRACEFUL

typedef struct nxt_unit_impl_s nxt_unit_impl_t;
typedef struct nxt_unit_mmap_s nxt_unit_mmap_t;
Expand Down
Loading
Loading