Skip to content

Instantly share code, notes, and snippets.

@matthewstory
Last active January 20, 2022 15:05
Show Gist options
  • Save matthewstory/4547282 to your computer and use it in GitHub Desktop.
Save matthewstory/4547282 to your computer and use it in GitHub Desktop.
Simple Pre-Forked Python JSON-RPC Server
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