{"id":8909,"library":"cotyledon","title":"Cotyledon","description":"Cotyledon is a Python framework (version 2.2.0, actively maintained) designed for defining and managing long-running services. It provides robust handling of Unix signals, efficient spawning and supervision of worker processes, daemon reloading capabilities, `sd-notify` integration, and rate limiting for worker restarts. It sees significant use in OpenStack Telemetry projects as a lightweight replacement for `oslo.service`, which carried heavy `eventlet` dependencies. The library aims for a consistent code path for single and multiple worker configurations and offers advanced reload and termination APIs.","status":"active","version":"2.2.0","language":"en","source_language":"en","source_url":"https://github.com/sileht/cotyledon","tags":["service management","daemon","process management","unix signals","long-running services","openstack"],"install":[{"cmd":"pip install cotyledon","lang":"bash","label":"Install stable version"}],"dependencies":[{"reason":"Recommended for periodic tasks if migrating from oslo.service, as cotyledon itself does not provide this functionality.","package":"futurist","optional":true},{"reason":"Used in some examples for configuration management, particularly when integrating with OpenStack projects, but not a core dependency for basic cotyledon services.","package":"oslo.config","optional":true}],"imports":[{"symbol":"Service","correct":"from cotyledon import Service"},{"symbol":"ServiceManager","correct":"from cotyledon import ServiceManager"}],"quickstart":{"code":"import cotyledon\nimport logging\nimport threading\nimport time\nimport os\n\nLOG = logging.getLogger(__name__)\n\nclass MyService(cotyledon.Service):\n    name = \"my_example_service\"\n\n    def __init__(self, worker_id):\n        super(MyService, self).__init__(worker_id)\n        self._shutdown = threading.Event()\n        LOG.info(f\"[{os.getpid()}] {self.name} worker {self.worker_id} init\")\n\n    def run(self):\n        LOG.info(f\"[{os.getpid()}] {self.name} worker {self.worker_id} running...\")\n        # In a real service, this loop would perform work, e.g., consume from a queue\n        # and call _shutdown.set() when it needs to stop processing.\n        while not self._shutdown.is_set():\n            LOG.debug(f\"[{os.getpid()}] {self.name} worker {self.worker_id} working...\")\n            time.sleep(1)\n\n    def terminate(self):\n        LOG.info(f\"[{os.getpid()}] {self.name} worker {self.worker_id} terminating...\")\n        self._shutdown.set()\n\n    def reload(self):\n        LOG.info(f\"[{os.getpid()}] {self.name} worker {self.worker_id} reloading...\")\n        # Implement logic to reload configuration or re-initialize components\n        # without stopping the worker if possible.\n\ndef main():\n    # Basic setup for logging to console\n    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(process)d - %(levelname)s - %(message)s')\n    LOG.info(\"Starting Cotyledon Service Manager\")\n\n    manager = cotyledon.ServiceManager()\n    # Add MyService with 2 worker processes\n    manager.add(MyService, workers=2)\n    # Run the service manager, which will spawn workers and handle signals\n    manager.run()\n    LOG.info(\"Cotyledon Service Manager stopped.\")\n\nif __name__ == \"__main__\":\n    main()\n","lang":"python","description":"This quickstart defines a simple `MyService` that logs its lifecycle events (`init`, `run`, `terminate`, `reload`). The `ServiceManager` then registers two workers for this service. The `run` method simulates work, waiting on a shutdown event. To stop the service, send a `SIGTERM` (e.g., Ctrl+C in a terminal) to the main process; for reload, send `SIGHUP` (e.g., `kill -HUP <pid>`)."},"warnings":[{"fix":"Rewrite concurrency patterns to use standard Python `threading` or `multiprocessing` modules. For periodic tasks previously handled by `oslo.service`, consider using the `futurist` library.","message":"When migrating from `oslo.service`, be aware that Cotyledon does not rely on `eventlet` for greenlet-based concurrency or monkey-patching the standard library. This means applications depending on `eventlet`'s behavior will need significant refactoring.","severity":"breaking","affected_versions":"All versions (design decision)"},{"fix":"Integrate a dedicated WSGI server (e.g., Gunicorn, uWSGI) and manage its processes separately or within your Cotyledon service, handling socket binding and passing through standard means.","message":"Cotyledon does not provide built-in facilities for WSGI application creation or socket sharing between parent and child processes. If your `oslo.service` based application relied on these features for HTTP services, Cotyledon is not a direct drop-in replacement.","severity":"gotcha","affected_versions":"All versions (design decision)"},{"fix":"Ensure only one instance of your main application entry point is executed. Check for existing processes before starting new ones, or design your deployment environment to guarantee single instance execution.","message":"The `ServiceManager` includes a 'seatbelt' mechanism to prevent multiple service managers from running concurrently, which can lead to unexpected behavior if multiple instances of your application are launched in the same environment.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Ensure your service class's `__init__` method signature is `def __init__(self, worker_id, *args, **kwargs):` and calls `super().__init__(worker_id, *args, **kwargs)`.","cause":"Your custom service class (inheriting from `cotyledon.Service`) must define an `__init__` method that accepts `worker_id` as its first argument after `self` and passes it to `super().__init__(worker_id)`.","error":"TypeError: __init__() missing 1 required positional argument: 'worker_id'"},{"fix":"Inspect worker logs for unhandled exceptions or error messages. Ensure your `run()` method properly handles its work loop and that the `terminate()` method gracefully shuts down resources. Increase logging level for the specific service to debug.","cause":"A worker process terminated prematurely. This can be due to unhandled exceptions within the worker's `run()` method, memory issues, or incorrect termination logic.","error":"Service process exited unexpectedly (exit code N)"},{"fix":"Override the `reload()` method in your `cotyledon.Service` subclass. This method is called when `SIGHUP` is received, allowing you to implement logic like reloading configuration files or re-initializing state without fully stopping and restarting the worker process. If no `reload()` method is defined, `cotyledon` will still restart the worker for a full reload.","cause":"Your custom service class has not implemented the `reload()` method, or the implementation does not include the desired reload logic.","error":"SIGHUP received but service does not reload"}]}