about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2015-11-11 01:43:06 +0000
committerEric Wong <e@80x24.org>2015-11-11 02:39:49 +0000
commit0312c1e6220ef4280268a0f48f24db90738037bd (patch)
treecd94cedf0acef03617ca5d56e788b74063a0edb6
parent97ade9d8d5d751c197b61faee5f3ae6589b6b432 (diff)
downloadcmogstored-0312c1e6220ef4280268a0f48f24db90738037bd.tar.gz
While I have my reservations about systemd, socket activation alone
is a good idea and we already have existing infrastructure for
supporting it in SIGUSR2 upgrades.

We are intentionally avoiding linkage to libsystemd to avoid dealing
with ABI compatibility issues between old and new systems.  This
also allows us to integrate more easily with non-systemd systems
which use the same environment variables as systemd.
-rw-r--r--cmogstored.x7
-rw-r--r--inherit.c56
-rw-r--r--test/inherit.rb32
3 files changed, 91 insertions, 4 deletions
diff --git a/cmogstored.x b/cmogstored.x
index bd5956d..a6b96e6 100644
--- a/cmogstored.x
+++ b/cmogstored.x
@@ -15,6 +15,13 @@ MOG_IOSTAT_CMD - command-line for invoking iostat(1).  This is used
 by the sidechannel to report disk utilization to trackers.
 Default: "iostat -dx 1 30"
 
+LISTEN_FDS, LISTEN_PID - may be used for systemd-style socket activation
+regardless of whether systemd is on the system.  See sd_listen_fds(3)
+for more information.  Since cmogstored supports two protocols on
+different sockets, the command line or config file must still specify
+which addresses to listen on.  This feature is available in cmogstored
+1.5.0 and later.
+
 See MALLOC TUNING for environment variables which may affect memory
 usage.
 
diff --git a/inherit.c b/inherit.c
index ca7afa7..0229a04 100644
--- a/inherit.c
+++ b/inherit.c
@@ -89,6 +89,55 @@ static bool listener_close_each(void *_l, void *unused)
         return true;
 }
 
+static void listeners_init(void)
+{
+        if (listeners) return;
+        listeners = hash_initialize(3, NULL, listener_hash, listener_cmp, free);
+        mog_oom_if_null(listeners);
+        atexit(listeners_cleanup);
+}
+
+static unsigned long listen_env(const char *env)
+{
+        const char *e = getenv(env);
+        unsigned long tmp;
+        char *end;
+
+        if (!e) return ULONG_MAX;
+        errno = 0;
+        tmp = strtoul(e, &end, 10);
+        if (errno) die_errno("failed to parse %s: %s", env, e);
+        if (*end) die("trailing byte in %s: %s", env, e);
+
+        return tmp;
+}
+
+/* systemd-style socket activation in the vein of sd_listen_fds(3) */
+static void systemd_inherit_fds(void)
+{
+        const int listen_fds_start = 3; /* SD_LISTEN_FDS_START */
+        int fd, listen_fds_end;
+        unsigned long tmp = listen_env("LISTEN_PID");
+
+        if (getpid() != (pid_t)tmp) goto out;
+
+        tmp = listen_env("LISTEN_FDS");
+        if (tmp > INT_MAX) die("LISTEN_FDS out of range: %lu", tmp);
+
+        listeners_init();
+        listen_fds_end = listen_fds_start + (int)tmp;
+        for (fd = listen_fds_start; fd < listen_fds_end; fd++) {
+                if (mog_set_cloexec(fd, true) == 0)
+                        register_listen_fd(fd);
+                else
+                        die("inherited out %d of %lu LISTEN_FDS",
+                            fd - listen_fds_start, tmp);
+        }
+out:
+        unsetenv("LISTEN_FDS");
+        unsetenv("LISTEN_PID");
+}
+
 /* close all inherited listeners we do not need */
 void mog_inherit_cleanup(void)
 {
@@ -131,13 +180,12 @@ void mog_inherit_init(void)
         unsigned long fd;
         unsigned endbyte;
 
+        systemd_inherit_fds();
+
         if (orig == NULL)
                 return;
 
-        listeners = hash_initialize(3, NULL, listener_hash, listener_cmp, free);
-        mog_oom_if_null(listeners);
-        atexit(listeners_cleanup);
-
+        listeners_init();
         fds = xstrdup(orig);
         tip = fds;
 
diff --git a/test/inherit.rb b/test/inherit.rb
index b342ab0..34aa52f 100644
--- a/test/inherit.rb
+++ b/test/inherit.rb
@@ -142,4 +142,36 @@ class TestInherit < Test::Unit::TestCase
     @err.rewind
     assert_match(/failed to parse/, @err.read)
   end
+
+  def test_inherit_systemd
+    # disabled test on old Rubies: https://bugs.ruby-lang.org/issues/11336
+    # [ruby-core:69895] [Bug #11336] fixed by r51576
+    return unless RUBY_VERSION.to_f >= 2.3
+
+    mgmt = TCPServer.new(@host, 0)
+    @to_close << mgmt
+    mport = mgmt.addr[1]
+    cmd = %W(cmogstored --docroot=#@tmpdir --httplisten=#@host:#@port
+             --mgmtlisten=#@host:#{mport} --maxconns=100)
+    @pid = fork do
+      ENV['LISTEN_PID'] = "#$$"
+      ENV['LISTEN_FDS'] = '2'
+      exec(*cmd, 3 => mgmt.fileno, 4 => @srv.fileno)
+    end
+
+    # just ensure HTTP works after being inherited
+    Net::HTTP.start(@host, @port) do |http|
+      [ Net::HTTP::Get, Net::HTTP::Head ].each do |meth|
+        resp = http.request(meth.new("/"))
+        assert_kind_of Net::HTTPOK, resp
+      end
+    end
+
+    # still works since drop is open in _this_ process
+    c = TCPSocket.new(@host, mport)
+    assert_instance_of(TCPSocket, c)
+    @to_close << c
+    c.write "hello\n"
+    assert_match /ERROR: unknown command/, c.gets
+  end
 end