unit icon indicating copy to clipboard operation
unit copied to clipboard

Idle timeout and prototype processes

Open dward opened this issue 3 years ago • 3 comments

We utilize unit to effectively scale to zero (spare processes set to 0) when certain applications are not busy.

With 1.26.x, there appears to be prototype processes that run per application. Is there a configuration setting to idle these out as well?

dward avatar Mar 14 '22 15:03 dward

With 1.26 the job of forking new application processes was moved from the unit: controller process to a prototype process for each application.

This scales better for multiple applications and languages. Reduces initialization time for new application processes, enables sharing of opcache for PHP across multiple applications, and enables future memory optimizations.

The prototype is the "application starter" (forking code) and cannot be idled-out without removing the application from the running configuration.

https://github.com/nginx/unit/commit/e207415a78ae67b937faf7e5bcd6e5192993180a

lcrilly avatar Mar 14 '22 17:03 lcrilly

Here is the patch to stop the prototype process when last idle process stopped:

diff --git a/src/nxt_router.c b/src/nxt_router.c
--- a/src/nxt_router.c
+++ b/src/nxt_router.c
@@ -4985,7 +4985,7 @@ nxt_router_adjust_idle_timer(nxt_task_t 
 {
     nxt_app_t           *app;
     nxt_bool_t          queued;
-    nxt_port_t          *port;
+    nxt_port_t          *port, *proto_port;
     nxt_msec_t          timeout, threshold;
     nxt_queue_link_t    *lnk;
     nxt_event_engine_t  *engine;
@@ -5045,12 +5045,35 @@ nxt_router_adjust_idle_timer(nxt_task_t 
         app->processes--;
         port->app = NULL;
 
+        if (app->port_hash_count == 0) {
+            proto_port = app->proto_port;
+            app->proto_port = NULL;
+            proto_port->app = NULL;
+
+        } else {
+            proto_port = NULL;
+        }
+
         nxt_thread_mutex_unlock(&app->mutex);
 
-        nxt_debug(task, "app '%V' send QUIT to idle port %PI",
-                  &app->name, port->pid);
-
-        nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, -1, 0, 0, NULL);
+        if (proto_port == NULL) {
+            nxt_debug(task, "app '%V' send QUIT to idle port %PI",
+                      &app->name, port->pid);
+
+            nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, -1, 0, 0,
+                                  NULL);
+
+        } else {
+            nxt_debug(task, "app '%V' send QUIT to idle proto port %PI",
+                      &app->name, proto_port->pid);
+
+            nxt_port_socket_write(task, proto_port, NXT_PORT_MSG_QUIT, -1, 0, 0,
+                                  NULL);
+
+            nxt_port_close(task, proto_port);
+
+            nxt_port_use(task, proto_port, -1);
+        }
 
         nxt_port_use(task, port, -1);
 

Be aware about possible racing condition: application process may start processing the request, but not yet acknowledge it to router, at the same time in router application idle timer executed and the process is terminated by router with QUIT message.

mar0x avatar Mar 15 '22 08:03 mar0x

Very much appreciated @mar0x ! - I'll give it a shot.

dward avatar Mar 15 '22 13:03 dward