GmCapsule [main]

SIGHUP reloads config and restarts workers

=> 6ac505666d0d67bd9bfa608111105253b6d7f907

diff --git a/gmcapsule/__init__.py b/gmcapsule/__init__.py
index decc1d8..7a98b98 100644
--- a/gmcapsule/__init__.py
+++ b/gmcapsule/__init__.py
@@ -508,11 +508,16 @@ class Config:
     """
 
     def __init__(self, config_path):
+        self.config_path = config_path
+        self.reload()
+
+    def reload(self):
         self.ini = configparser.ConfigParser()
-        if os.path.exists(config_path):
-            self.ini.read(config_path)
+        if os.path.exists(self.config_path):
+            print('Configuration:', self.config_path)
+            self.ini.read(self.config_path)
         else:
-            print(config_path, 'not found -- using defaults')
+            print(self.config_path, 'not found -- using defaults')
 
     def hostnames(self):
         """
diff --git a/gmcapsule/gemini.py b/gmcapsule/gemini.py
index ecddbeb..a7026a0 100644
--- a/gmcapsule/gemini.py
+++ b/gmcapsule/gemini.py
@@ -11,6 +11,7 @@ import socket
 import threading
 import queue
 import re
+import signal
 import time
 from pathlib import Path
 from urllib.parse import urlparse
@@ -659,22 +660,25 @@ class Worker(threading.Thread):
             return
 
 
+_server_instance = None
+
+def _restart_workers(signum, frame):
+    if signum == signal.SIGHUP:
+        _server_instance.restart_workers()
+
+
 class Server:
     def __init__(self, cfg):
+        global _server_instance
+        assert _server_instance is None
+        _server_instance = self
         #mp.set_start_method('spawn')
+        self.cfg = cfg
+        self.address = cfg.address()
+        self.port = cfg.port()
 
-        hostname_or_hostnames = cfg.hostnames()
         cert_path = cfg.certs_dir() / 'cert.pem'
         key_path = cfg.certs_dir() / 'key.pem'
-        address = cfg.address()
-        port = cfg.port()
-        session_id = f'GmCapsule:{cfg.port()}'.encode('utf-8')
-        num_threads = cfg.num_threads()
-
-        self.hostnames = [hostname_or_hostnames] \
-            if type(hostname_or_hostnames) == str else hostname_or_hostnames
-        self.address = address
-        self.port = port
 
         if not os.path.exists(cert_path):
             raise Exception("certificate file not found: " + str(cert_path))
@@ -685,22 +689,32 @@ class Server:
         self.context.use_certificate_file(str(cert_path))
         self.context.use_privatekey_file(str(key_path))
         self.context.set_verify(SSL.VERIFY_PEER, verify_callback)
-        if session_id:
-            if type(session_id) != bytes:
-                raise Exception("session_id type must be `bytes`")
-            self.context.set_session_id(session_id)
+
+        session_id = f'GmCapsule:{cfg.port()}'.encode('utf-8')
+        #if type(session_id) != bytes:
+        #    raise Exception("session_id type must be `bytes`")
+        self.context.set_session_id(session_id)
 
         # Spawn the worker threads.
         self.shutdown_event = threading.Event()
         self.workers = []
         self.work_queue = queue.Queue()
-        for worker_id in range(max(num_threads, 1)):
-            worker = Worker(worker_id, cfg, self.work_queue, self.shutdown_event)
-            self.workers.append(worker)
+        self.create_workers(cfg)
 
         self.sock = None
         self.sv_conn = None
 
+    def restart_workers(self):
+        """
+        Restarts workers with an updated configuration. The server socket or
+        TLS configuration are not modified, even if the values have changed
+        in the configuration file.
+        """
+        self.stop_workers()
+        self.cfg.reload()
+        self.create_workers(self.cfg)
+        self.start_workers()
+
     def run(self):
         attempts = 60
         print(f'Opening port {self.port}...')
@@ -720,14 +734,12 @@ class Server:
                 print('...')
         print(f'Server started on port {self.port}')
 
-        PARALLELIZE = True
-
-        if PARALLELIZE:
-            for worker in self.workers:
-                worker.start()
-            print(len(self.workers), 'worker(s) started')
+        self.start_workers()
 
-        snapshot = None
+        try:
+            signal.signal(signal.SIGHUP, _restart_workers)
+        except ValueError:
+            print('Restarting with SIGHUP not supported')
 
         while True:
             stream = None
@@ -735,11 +747,6 @@ class Server:
                 stream, from_addr = self.sv_conn.accept()
                 stream._socket.settimeout(10)
                 self.work_queue.put((stream, from_addr))
-
-                if not PARALLELIZE:
-                    self.work_queue.put((None, None)) # single iteration only
-                    self.workers[0].run()
-
                 del stream
                 del from_addr
             except KeyboardInterrupt:
@@ -756,11 +763,27 @@ class Server:
         self.sock = None
 
         # Stop all workers.
-        self.shutdown_event.set()
-        if PARALLELIZE:
-            for i in range(len(self.workers)):
-                self.work_queue.put((None, None))
-            for worker in self.workers:
-                worker.join()
+        self.stop_workers()
 
         print('Done')
+
+    def create_workers(self, cfg):
+        self.shutdown_event.clear()
+        for worker_id in range(max(cfg.num_threads(), 1)):
+            worker = Worker(worker_id, cfg, self.work_queue, self.shutdown_event)
+            self.workers.append(worker)
+
+    def start_workers(self):
+        for worker in self.workers:
+            worker.start()
+        print(len(self.workers), 'worker(s) started')
+
+    def stop_workers(self):
+        self.shutdown_event.set()
+        n = len(self.workers)
+        for _ in range(n):
+            self.work_queue.put((None, None))
+        for worker in self.workers:
+            worker.join()
+        self.workers = []
+        print(n, 'worker(s) stopped')
Proxy Information
Original URL
gemini://git.skyjake.fi/gmcapsule/main/cdiff/6ac505666d0d67bd9bfa608111105253b6d7f907
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
29.152192 milliseconds
Gemini-to-HTML Time
0.429206 milliseconds

This content has been proxied by September (ba2dc).