]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
socket-proxy: Support exit-on-idle
authorEric Anderson <ejona86@gmail.com>
Sat, 2 May 2020 22:54:24 +0000 (15:54 -0700)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 6 May 2020 11:58:57 +0000 (13:58 +0200)
This adds the --exit-idle-time argument that causes
systemd-socket-proxyd to exit when there has been an idle period. An
open connection prevents the idle period from starting, even if there is
no activity on that connection.

When combined with another service that uses StopWhenUnneeded=, the
proxy exiting can trigger a resource-intensive process to exit. So
although the proxy may consume minimal resources, significant resources
can be saved indirectly.

Fixes #2106

man/systemd-socket-proxyd.xml
src/socket-proxy/socket-proxyd.c

index a72ac1bbc66fcf2276d9a215897834c48894aec4..58b26aad87f4e785de9e5f6d4710f808ecf85a0b 100644 (file)
         <listitem><para>Sets the maximum number of simultaneous connections, defaults to 256.
         If the limit of concurrent connections is reached further connections will be refused.</para></listitem>
       </varlistentry>
+      <varlistentry>
+        <term><option>--exit-idle-time=</option></term>
+
+        <listitem><para>Sets the time before exiting when there are no connections, defaults to
+        <constant>infinity</constant>. Takes a unit-less value in seconds, or a time span value such
+        as <literal>5min 20s</literal>.</para></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
   <refsect1>
@@ -115,6 +122,9 @@ server {
         <programlisting><![CDATA[# systemctl enable --now proxy-to-nginx.socket
 $ curl http://localhost:80/]]></programlisting>
       </example>
+      <para>If <filename>nginx.service</filename> has <varname>StopWhenUnneeded=</varname> set, then
+      passing <option>--exit-idle-time=</option> to <command>systemd-socket-proxyd</command> allows
+      both services to stop during idle periods.</para>
     </refsect2>
     <refsect2>
       <title>Namespace Example</title>
index 2ee6fc2f0a6aa0fcf4fbf2539cc4ba475875262b..7b548c50768d5a4e50b12477d3ccf30ffb4ebd9c 100644 (file)
 
 static unsigned arg_connections_max = 256;
 static const char *arg_remote_host = NULL;
+static usec_t arg_exit_idle_time = USEC_INFINITY;
 
 typedef struct Context {
         sd_event *event;
         sd_resolve *resolve;
+        sd_event_source *idle_time;
 
         Set *listen;
         Set *connections;
@@ -75,6 +77,51 @@ static void connection_free(Connection *c) {
         free(c);
 }
 
+static int idle_time_cb(sd_event_source *s, uint64_t usec, void *userdata) {
+        Context *c = userdata;
+        int r;
+
+        if (!set_isempty(c->connections)) {
+                log_warning("Idle timer fired even though there are connections, ignoring");
+                return 0;
+        }
+
+        r = sd_event_exit(c->event, 0);
+        if (r < 0) {
+                log_warning_errno(r, "Error while stopping event loop, ignoring: %m");
+                return 0;
+        }
+        return 0;
+}
+
+static int connection_release(Connection *c) {
+        int r;
+        Context *context = c->context;
+        usec_t idle_instant;
+
+        connection_free(c);
+
+        if (arg_exit_idle_time < USEC_INFINITY && set_isempty(context->connections)) {
+                idle_instant = usec_add(now(CLOCK_MONOTONIC), arg_exit_idle_time);
+                if (context->idle_time) {
+                        r = sd_event_source_set_time(context->idle_time, idle_instant);
+                        if (r < 0)
+                                return log_error_errno(r, "Error while setting idle time: %m");
+
+                        r = sd_event_source_set_enabled(context->idle_time, SD_EVENT_ONESHOT);
+                        if (r < 0)
+                                return log_error_errno(r, "Error while enabling idle time: %m");
+                } else {
+                        r = sd_event_add_time(context->event, &context->idle_time, CLOCK_MONOTONIC,
+                                              idle_instant, 0, idle_time_cb, context);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to create idle timer: %m");
+                }
+        }
+
+        return 0;
+}
+
 static void context_clear(Context *context) {
         assert(context);
 
@@ -83,6 +130,7 @@ static void context_clear(Context *context) {
 
         sd_event_unref(context->event);
         sd_resolve_unref(context->resolve);
+        sd_event_source_unref(context->idle_time);
 }
 
 static int connection_create_pipes(Connection *c, int buffer[static 2], size_t *sz) {
@@ -206,7 +254,7 @@ static int traffic_cb(sd_event_source *s, int fd, uint32_t revents, void *userda
         return 1;
 
 quit:
-        connection_free(c);
+        connection_release(c);
         return 0; /* ignore errors, continue serving */
 }
 
@@ -269,7 +317,7 @@ static int connection_complete(Connection *c) {
         return 0;
 
 fail:
-        connection_free(c);
+        connection_release(c);
         return 0; /* ignore errors, continue serving */
 }
 
@@ -299,7 +347,7 @@ static int connect_cb(sd_event_source *s, int fd, uint32_t revents, void *userda
         return connection_complete(c);
 
 fail:
-        connection_free(c);
+        connection_release(c);
         return 0; /* ignore errors, continue serving */
 }
 
@@ -343,7 +391,7 @@ static int connection_start(Connection *c, struct sockaddr *sa, socklen_t salen)
         return 0;
 
 fail:
-        connection_free(c);
+        connection_release(c);
         return 0; /* ignore errors, continue serving */
 }
 
@@ -361,7 +409,7 @@ static int resolve_handler(sd_resolve_query *q, int ret, const struct addrinfo *
         return connection_start(c, ai->ai_addr, ai->ai_addrlen);
 
 fail:
-        connection_free(c);
+        connection_release(c);
         return 0; /* ignore errors, continue serving */
 }
 
@@ -409,7 +457,7 @@ static int resolve_remote(Connection *c) {
         return 0;
 
 fail:
-        connection_free(c);
+        connection_release(c);
         return 0; /* ignore errors, continue serving */
 }
 
@@ -426,6 +474,12 @@ static int add_connection_socket(Context *context, int fd) {
                 return 0;
         }
 
+        if (context->idle_time) {
+                r = sd_event_source_set_enabled(context->idle_time, SD_EVENT_OFF);
+                if (r < 0)
+                        log_warning_errno(r, "Unable to disable idle timer, continuing: %m");
+        }
+
         r = set_ensure_allocated(&context->connections, NULL);
         if (r < 0) {
                 log_oom();
@@ -535,9 +589,13 @@ static int add_listen_socket(Context *context, int fd) {
 
 static int help(void) {
         _cleanup_free_ char *link = NULL;
+        _cleanup_free_ char *time_link = NULL;
         int r;
 
         r = terminal_urlify_man("systemd-socket-proxyd", "8", &link);
+        if (r < 0)
+                return log_oom();
+        r = terminal_urlify_man("systemd.time", "7", &time_link);
         if (r < 0)
                 return log_oom();
 
@@ -545,11 +603,14 @@ static int help(void) {
                "%1$s [SOCKET]\n\n"
                "Bidirectionally proxy local sockets to another (possibly remote) socket.\n\n"
                "  -c --connections-max=  Set the maximum number of connections to be accepted\n"
+               "     --exit-idle-time=   Exit when without a connection for this duration. See\n"
+               "                         the %3$s for time span format\n"
                "  -h --help              Show this help\n"
                "     --version           Show package version\n"
                "\nSee the %2$s for details.\n"
                , program_invocation_short_name
                , link
+               , time_link
         );
 
         return 0;
@@ -559,11 +620,13 @@ static int parse_argv(int argc, char *argv[]) {
 
         enum {
                 ARG_VERSION = 0x100,
+                ARG_EXIT_IDLE,
                 ARG_IGNORE_ENV
         };
 
         static const struct option options[] = {
                 { "connections-max", required_argument, NULL, 'c'           },
+                { "exit-idle-time",  required_argument, NULL, ARG_EXIT_IDLE },
                 { "help",            no_argument,       NULL, 'h'           },
                 { "version",         no_argument,       NULL, ARG_VERSION   },
                 {}
@@ -597,6 +660,12 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case ARG_EXIT_IDLE:
+                        r = parse_sec(optarg, &arg_exit_idle_time);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", optarg);
+                        break;
+
                 case '?':
                         return -EINVAL;