{ "info": { "author": "Micha\u0142 Jaworski", "author_email": "swistakm@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "License :: OSI Approved :: BSD License", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Topic :: System :: Systems Administration" ], "description": "hupwatch\n========\n\nSimple utility for graceful reloading of services.\n\nGeneral usage bases on my other project\n`ianitor `__. **hupwatch** is a\nsimple shell command that wraps process and can be simply used in your\nexisting process/service supervision tool like\n`supervisord `__,\n`circus `__,\n`runit `__ etc.\n\nwhy?\n----\n\nReloading of web application code gracefully is a today's must. Every\nDevOps should tell you that already. Many of Python web servers allow\nsuch scenario but handle this differently. Usually you end up with only\nfew reasonable options:\n\n- `gunicorn `__ allows to do this\n by simply sending Unix ``HUP`` signal to the gunicorn process. It\n will gracefully stop existing workers and start new ones with new\n version of code. Other choice is to send ``USR2`` signal that will\n spawn new master gunicorn process with set of udpated workers. You\n are then able to\n- `uWSGI `__ gives few options and the\n best one is very similar in effect to sending ``USR2`` to\n ``gunicorn``. uWSGI provides very verbose documentation on that. One\n could say \"too verbore\". Good luck on understanding it!\n- You may depend on load balancing on the higher level your\n architecture stack. Still you need to be able to finish current\n requests that are being processed during the deployment. So you\n require a web server that can at least gracefully shutdown its\n workers.\n\nGraceful reloading implementations provided by gunicorn and uWSGI are\nboth neat but fail in reality when you try to automate things or use it\nwith any of the popular tools for process/service supervision:\n\n- Gunicorn's HUP-reload will fail if you switch your codebase using\n symlinks. This is a very popular technique in many organizations.\n Gunicorn will also it will not update gunicorn its own configuration\n on ``HUP`` but will only restart workers with a new code. This\n approach will work fine if you simply replace the code *in situ* and\n do not need to tune configuration options like worker class or\n concurrency settings. Still, I wish you a good luck with that if you\n use Django and you did a lot of updates to templates with your latest\n release. Old gunicorn workers will \"gracefuly\" shutdown but may also\n \"gracefully\" respond with server errors if you replaced code in\n place. Gunicorn accepts a ``--chdir`` argument that should fix that\n but it again does not play well with some popular deploment solutions\n like buildout.\n- The second approach (spawning new master server process) seems to be\n a better way and generally works well with symlinks but does not play\n well with many process supervision tools. The most popular one in\n Python world - supervisord - must spawn a subprocess by itself in\n order to be able to control it. Once gunicorn (after ``USR2`` signal)\n or uWSGI (after whatever it requires) spawns (re-execs) a new master\n process it has no longer the supervisor as its parent. In short, it\n will eventually become the child process of ``init`` and there is no\n way to \"adopt\" such process by supervisor. Supervisor will try to\n spawn it again and you end up with twice more workers than you\n expected. If you won't notice that and perform many subsequent\n reloads in that manner you may eventually run out of resources.\n- Generally handling updates with the help of load balancer seems like\n a safer solution because you can simply restart web servers once they\n are removed from your stack. This is unfortunately a lot harder to\n automate and adds additional level of complexity to your whole\n operations.\n\nYou may of course work around these issues by providing some custom\nintegration for whatever process supervision tool you are using or do\nsome crazy next/current instance switching and mantain doubled\nconfiguration of services only for this single purpose. This will make\nyour solution either non-portable to other tools or make the whole\nsolution harder to automate in a reliable way.\n\nhow does it work?\n-----------------\n\n**hupwatch** provides a single solution that can be easily integrated\nwith virtually any supervision tool and allows to reload whole web\nservers with only single command that is available on any POSIX system.\nIt is ``kill``:\n\n::\n\n # on terminal or inside supervisord config:\n $ hupwatch -- gunicorn myapp:application --bind unix:/tmp/myapp.sock\n => HUP watch [INFO ]: Starting HUP watch (92808)\n => HUP watch [INFO ]: Child process 92809 started\n => HUP watch [INFO ]: Pausing for signal\n [2016-01-27 17:24:13 +0100] [92809] [INFO] Starting gunicorn 19.4.5\n [2016-01-27 17:24:13 +0100] [92809] [INFO] Listening at: unix:/tmp/myapp.sock (92809)\n [2016-01-27 17:24:13 +0100] [92809] [INFO] Using worker: sync\n [2016-01-27 17:24:13 +0100] [92812] [INFO] Booting worker with pid: 92812\n\n # issued on any other terminal:\n kill -HUP \n\n # continued result in the hupwatch stdout:\n [...]\n => HUP watch [DEBUG ]: HUP: >>>\n => HUP watch [DEBUG ]: HUP: Waiting for process (92955) to warm up\n => HUP watch [DEBUG ]: HUP: Sending SIGTERM to old process (92809)\n => HUP watch [DEBUG ]: HUP: Waiting for process (92809) to quit...\n [2016-01-27 17:24:46 +0100] [92809] [INFO] Handling signal: term\n [2016-01-27 17:24:46 +0100] [92955] [INFO] Starting gunicorn 19.4.5\n [2016-01-27 17:24:46 +0100] [92955] [INFO] Listening at: unix:/tmp/myapp.sock (92955)\n [2016-01-27 17:24:46 +0100] [92955] [INFO] Using worker: sync\n [2016-01-27 17:24:46 +0100] [92964] [INFO] Booting worker with pid: 92964\n [2016-01-27 17:24:58 +0100] [92812] [INFO] Worker exiting (pid: 92812)\n [2016-01-27 17:24:58 +0100] [92809] [INFO] Shutting down: Master\n => HUP watch [DEBUG ]: CHLD: >>>\n => HUP watch [INFO ]: CHLD: Child process quit\n => HUP watch [DEBUG ]: CHLD: <<<\n => HUP watch [INFO ]: HUP: Old process quit with code: 0\n => HUP watch [DEBUG ]: HUP: <<<\n => HUP watch [INFO ]: Pausing for signal\n\n**hupwatch** will start anything that you have provided after the ``--``\ncharacters as its own subprocess (using ``subprocess.Popen()``) and\nlistens for incoming Unix signals. Whenever it gets a ``HUP`` signal it\nstarts a new process with the same arguments and sends ``TERM`` signal\nto the process that was started previously so it can shutdown gracefuly.\n\nThis make the whole realoading process very easy to automate. No need to\nexecute multiple commands and mantain any state between them. Simply\nHUP'n'go! This is a good news for\n`fabric `__ enthusiasts - don't need\nto worry about lost shh connection during whole reload procedure because\nit takes only one step (at least if you use symlinks). There is nothing\nto interrupt!\n\nRolling back the update is also painless: simply change project symlink\nand issue another ``HUP`` signal to the same hupwatch pid. Auto rollback\nshould be also easy to implement and we are open to any contributions!\n\nIf you paid attention then you should already notice that this requires\nonly two things to make it working as a solution for graceful reload:\n\n- You need to use Unix sockets instead of ports so both old and new\n processes can bind to the same address\n- Your web server needs to perform a graceful shutdown when it receives\n ``TERM`` signal. Gunicorn already does that. uWSGI is not supported\n yet.\n\nanything else?\n--------------\n\nSee the usage with ``hupwatch --help`` for more information on possible\nconfiguration options:\n\nThere is also some details important detail of handling failures and\nwhat to do when **hupwatch** receives other signals (e.g. ``KILL``,\n``TERM``, ``INT``). By default it assumes that you want to have have\nyour process working no matter what happens with the parent (hupwatch).\nSo in case of failure it leaves it as it is - spawned process will\nbecome a child of ``init``. If you that this happened you can clean up\nthe mess manually without interrupting the process of serving web\nrequests. This behaviour can be changed with ``--kill-at-exit`` flag.\n\nstatus of this project?\n-----------------------\n\nThis is more a proof of concept than a battle-tested tool. Anyway, there\nare only few lines of code that actually do any work. Most of the code\nin this package is extensive logging and parsing of arguments. This\nstate of this package will eventually change in a near future, because\nit solves a real problem that we have in my organization. So give it a\ntry at least in your staging/testing environment.\n\nContributions are really welcome!", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/swistakm/hupwatch", "keywords": null, "license": "BSD", "maintainer": null, "maintainer_email": null, "name": "hupwatch", "package_url": "https://pypi.org/project/hupwatch/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/hupwatch/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/swistakm/hupwatch" }, "release_url": "https://pypi.org/project/hupwatch/0.0.3/", "requires_dist": null, "requires_python": null, "summary": "Simple process supervision agnostic utility for graceful reloading of services", "version": "0.0.3" }, "last_serial": 1925899, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "18d7a39232672481ef6f661d3154090e", "sha256": "9afe50a78c1811776aa3a30d1ad001001f9243b555195fe24565eb45b69109e8" }, "downloads": -1, "filename": "hupwatch-0.0.1.tar.gz", "has_sig": false, "md5_digest": "18d7a39232672481ef6f661d3154090e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6469, "upload_time": "2016-01-27T15:41:27", "url": "https://files.pythonhosted.org/packages/2f/c0/1c26282e6317e12a63fc518cd5dc2ac2284c817db9f44d14385311cce39b/hupwatch-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "eb5ba51e1e25c0a14d276eabc158eaa4", "sha256": "06548340a308d44baf8c662373d7064a7b667a50c6bb25b34a0d9e4571a010d4" }, "downloads": -1, "filename": "hupwatch-0.0.2.tar.gz", "has_sig": false, "md5_digest": "eb5ba51e1e25c0a14d276eabc158eaa4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8085, "upload_time": "2016-01-27T15:44:48", "url": "https://files.pythonhosted.org/packages/43/15/f0363afb3816085d77ae8d6f8fe8a4a964cf7ad1092a888b4f97f0eeb440/hupwatch-0.0.2.tar.gz" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "9c5c7df6e58b583b85ec7b1ef812bfed", "sha256": "79d5a825eae57e1264adca9882202257343d1dc495c61d34f7234a0cb276513d" }, "downloads": -1, "filename": "hupwatch-0.0.3-py2-none-any.whl", "has_sig": false, "md5_digest": "9c5c7df6e58b583b85ec7b1ef812bfed", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 13219, "upload_time": "2016-01-27T17:13:52", "url": "https://files.pythonhosted.org/packages/46/b4/ed09fc1a8f62cc89abac84edaea0d46c0dfca11e0fab97c29b38f7614f86/hupwatch-0.0.3-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "142fd81f70ac0040f8a49a02d0079037", "sha256": "3d411ad2ece401b598d544a2dec5aa2594df9b9eef19a83aa5951edf4e9da5c9" }, "downloads": -1, "filename": "hupwatch-0.0.3.tar.gz", "has_sig": false, "md5_digest": "142fd81f70ac0040f8a49a02d0079037", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9804, "upload_time": "2016-01-27T17:13:04", "url": "https://files.pythonhosted.org/packages/fe/f8/863ce323017dd552b105fe4ff5f9d13bfc49ce60f3e04b5206f4e0783b10/hupwatch-0.0.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "9c5c7df6e58b583b85ec7b1ef812bfed", "sha256": "79d5a825eae57e1264adca9882202257343d1dc495c61d34f7234a0cb276513d" }, "downloads": -1, "filename": "hupwatch-0.0.3-py2-none-any.whl", "has_sig": false, "md5_digest": "9c5c7df6e58b583b85ec7b1ef812bfed", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 13219, "upload_time": "2016-01-27T17:13:52", "url": "https://files.pythonhosted.org/packages/46/b4/ed09fc1a8f62cc89abac84edaea0d46c0dfca11e0fab97c29b38f7614f86/hupwatch-0.0.3-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "142fd81f70ac0040f8a49a02d0079037", "sha256": "3d411ad2ece401b598d544a2dec5aa2594df9b9eef19a83aa5951edf4e9da5c9" }, "downloads": -1, "filename": "hupwatch-0.0.3.tar.gz", "has_sig": false, "md5_digest": "142fd81f70ac0040f8a49a02d0079037", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9804, "upload_time": "2016-01-27T17:13:04", "url": "https://files.pythonhosted.org/packages/fe/f8/863ce323017dd552b105fe4ff5f9d13bfc49ce60f3e04b5206f4e0783b10/hupwatch-0.0.3.tar.gz" } ] }