Last active
January 20, 2022 15:05
-
-
Save matthewstory/4547282 to your computer and use it in GitHub Desktop.
Simple Pre-Forked Python JSON-RPC Server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import signal | |
import sys | |
import select | |
import errno | |
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer | |
# BOOK-KEEPING | |
_PIDS = [] | |
def _kronos(signum, frame): | |
'''As Kronos did before us, we too may have to devour our children''' | |
for pid in _PIDS: | |
try: | |
os.kill(pid, signum) | |
except OSError, e: | |
if e.errno == errno.ESRCH: | |
_PIDS.remove(pid) | |
# wait on all child processes | |
while len(_PIDS): | |
pid, rc = os.waitpid(-1, 0) | |
_PIDS.remove(pid) | |
# exit non-zero to signal abnormal termination | |
sys.exit(1) | |
def _gogentle(signum, frame): | |
'''Do not throw a KeyboardInterrupt Error''' | |
os._exit(1) | |
# API | |
def add(x, y): | |
'''Add x and y''' | |
return x + y | |
def subtract(x, y): | |
'''Subtract y from x''' | |
return x - y | |
# server | |
def main(name, *argv): | |
global _PIDS | |
# JSON-RPC over HTTP on INET socket localhost:8888 | |
# under the hood, this calls `socket.bind` then `socket.listen` | |
s = SimpleJSONRPCServer(( argv[0], int(argv[1]), )) | |
# register our logging math functions | |
for fn in ( add, subtract, ): | |
s.register_function(fn) | |
# simple pre-fork server, fork before accept | |
for i in range(int(argv[2])): | |
# fork our current process | |
pid = os.fork() | |
# if we are the child fork ... | |
if 0 == pid: | |
# die without unhandled exception | |
for signum in ( signal.SIGINT, signal.SIGTERM, ): | |
signal.signal(signum, _gogentle) | |
# under the hood, this calls `socket.accept` | |
s.serve_forever() | |
os._exit(0) | |
# if we are the papa fork | |
else: | |
_PIDS.append(pid) | |
# setup signal relaying for INT and TERM | |
for signum in ( signal.SIGINT, signal.SIGTERM, ): | |
signal.signal(signum, _kronos) | |
# wait on the kids | |
while len(_PIDS): | |
# 1s timeout here means we're checking for exiting children at most | |
# 1x per second, prevents a busy loop | |
reads, _, _ = select.select([sys.stdin], [], [], 1) | |
if sys.stdin in reads: | |
# blocking, read 1 line | |
cmd = sys.stdin.readline() | |
# kill ourselves ... kronos will propegate | |
if cmd.strip() == 'exit': | |
os.kill(os.getpid(), signal.SIGTERM) | |
# check for exited children, non-blocking | |
while True: | |
pid, rc = os.waitpid(-1, os.WNOHANG) | |
if not pid: | |
break | |
_PIDS.remove(pid) | |
return 0 | |
if __name__ == '__main__': | |
sys.exit(main(*sys.argv)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment