PageRenderTime 64ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/src/python/osrf/server.py

https://gitlab.com/evergreen-bjwebb/opensrf-debian
Python | 489 lines | 309 code | 101 blank | 79 comment | 54 complexity | fba133f219bf62ba7d3e40ebbd3c144e MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
  1. """
  2. Implements an OpenSRF forking request server
  3. """
  4. # -----------------------------------------------------------------------
  5. # Copyright (C) 2008-2010 Equinox Software, Inc.
  6. # Bill Erickson <erickson@esilibrary.com>
  7. #
  8. # This program is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU General Public License
  10. # as published by the Free Software Foundation; either version 2
  11. # of the License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  21. # 02110-1301, USA
  22. # -----------------------------------------------------------------------
  23. import os, sys, fcntl, socket, errno, signal, time
  24. import osrf.log, osrf.conf, osrf.net, osrf.system
  25. import osrf.stack, osrf.app, osrf.const
  26. # used to define the size of the PID/size leader in
  27. # status and data messages passed to and from children
  28. SIZE_PAD = 12
  29. class Controller(object):
  30. '''
  31. OpenSRF forking request server.
  32. '''
  33. def __init__(self, service):
  34. '''Initialize the Controller object'''
  35. self.service = service # service name
  36. self.max_requests = 0 # max child requests
  37. self.max_children = 0 # max num of child processes
  38. self.min_children = 0 # min num of child processes
  39. self.num_children = 0 # current num children
  40. self.osrf_handle = None # xmpp handle
  41. self.routers = [] # list of registered routers
  42. self.keepalive = 0 # how long to wait for subsequent, stateful requests
  43. self.active_list = [] # list of active children
  44. self.idle_list = [] # list of idle children
  45. self.pid_map = {} # map of pid -> child object for faster access
  46. # Global status socketpair. All children relay their
  47. # availability info to the parent through this socketpair.
  48. self.read_status, self.write_status = socket.socketpair()
  49. self.read_status.setblocking(0)
  50. def cleanup(self):
  51. ''' Closes management sockets, kills children, reaps children, exits '''
  52. osrf.log.log_info("server: shutting down...")
  53. self.cleanup_routers()
  54. self.read_status.shutdown(socket.SHUT_RDWR)
  55. self.write_status.shutdown(socket.SHUT_RDWR)
  56. self.read_status.close()
  57. self.write_status.close()
  58. for child in self.idle_list + self.active_list:
  59. child.read_data.shutdown(socket.SHUT_RDWR)
  60. child.write_data.shutdown(socket.SHUT_RDWR)
  61. child.read_data.close()
  62. child.write_data.close()
  63. os.kill(child.pid, signal.SIGKILL)
  64. self.reap_children(True)
  65. os._exit(0)
  66. def handle_signals(self):
  67. ''' Installs SIGINT and SIGTERM handlers '''
  68. def handler(signum, frame):
  69. ''' Handler implementation '''
  70. self.cleanup()
  71. signal.signal(signal.SIGINT, handler)
  72. signal.signal(signal.SIGTERM, handler)
  73. def run(self):
  74. ''' Run the OpenSRF service, spawning and reaping children '''
  75. osrf.net.get_network_handle().disconnect()
  76. osrf.net.clear_network_handle()
  77. self.spawn_children()
  78. self.handle_signals()
  79. # give children a chance to connect before we start taking data
  80. time.sleep(.5)
  81. self.osrf_handle = osrf.system.System.net_connect(
  82. resource = '%s_listener' % self.service,
  83. service = self.service
  84. )
  85. # clear the recv callback so inbound messages do not filter
  86. # through the opensrf stack
  87. self.osrf_handle.receive_callback = None
  88. # connect to our listening routers
  89. self.register_routers()
  90. try:
  91. osrf.log.log_internal("server: entering main server loop...")
  92. while True: # main server loop
  93. self.reap_children()
  94. self.check_status()
  95. data = self.osrf_handle.recv(-1).to_xml()
  96. child = None
  97. if len(self.idle_list) > 0:
  98. child = self.idle_list.pop()
  99. self.active_list.append(child)
  100. osrf.log.log_internal(
  101. "server: sending data to available child %d" % child.pid
  102. )
  103. elif self.num_children < self.max_children:
  104. child = self.spawn_child(True)
  105. osrf.log.log_internal(
  106. "server: sending data to new child %d" % child.pid
  107. )
  108. else:
  109. osrf.log.log_warn("server: no children available, \
  110. waiting... consider increasing max_children for this application higher than \
  111. %d in the OpenSRF configuration if this message occurs frequently" \
  112. % self.max_children)
  113. child = self.check_status(True)
  114. self.write_child(child, data)
  115. except KeyboardInterrupt:
  116. osrf.log.log_info("server: exiting with keyboard interrupt")
  117. except Exception, exc:
  118. osrf.log.log_error(
  119. "server: exiting with exception: %s" % exc.message
  120. )
  121. finally:
  122. self.cleanup()
  123. def write_child(self, child, data):
  124. ''' Sends data to the child process '''
  125. try:
  126. child.write_data.sendall(data)
  127. except Exception, ex:
  128. osrf.log.log_error(
  129. "server: error sending data to child %d: %s"
  130. % (child.pid, str(ex))
  131. )
  132. self.cleanup_child(child.pid, True)
  133. return False
  134. return True
  135. def check_status(self, wait=False):
  136. ''' Checks to see if any children have indicated they are done with
  137. their current request. If wait is true, wait indefinitely
  138. for a child to be free. '''
  139. ret_child = None
  140. if wait:
  141. self.read_status.setblocking(1)
  142. while True:
  143. pid = None
  144. try:
  145. pid = self.read_status.recv(SIZE_PAD)
  146. except socket.error, exc:
  147. if exc.args[0] == errno.EAGAIN:
  148. break # no data left to read in nonblocking mode
  149. osrf.log.log_error(
  150. "server: child status check failed: %s" % str(exc)
  151. )
  152. if not wait or ret_child:
  153. break
  154. finally:
  155. if wait and ret_child:
  156. # we've received a status update from at least
  157. # 1 child. No need to block anymore.
  158. self.read_status.setblocking(0)
  159. if pid:
  160. child = self.pid_map[int(pid)]
  161. osrf.log.log_internal(
  162. "server: child process %d reporting for duty" % child.pid
  163. )
  164. if wait and ret_child is None:
  165. # caller is waiting for a free child;
  166. # leave it in the active list
  167. ret_child = child
  168. else:
  169. self.active_list.remove(child)
  170. self.idle_list.append(child)
  171. return ret_child
  172. def reap_children(self, done=False):
  173. '''
  174. Uses waitpid() to reap the children. If necessary, spawns new children.
  175. '''
  176. options = 0
  177. if not done:
  178. options = os.WNOHANG
  179. while True:
  180. try:
  181. (pid, status) = os.waitpid(0, options)
  182. if pid == 0:
  183. if not done:
  184. self.spawn_children()
  185. return
  186. osrf.log.log_internal("server: cleaning up child %d" % pid)
  187. self.num_children -= 1
  188. self.cleanup_child(pid)
  189. except OSError:
  190. return
  191. def cleanup_child(self, pid, kill=False):
  192. '''
  193. Removes the child from the active or idle list.
  194. Kills the process if requested.
  195. '''
  196. if kill:
  197. os.kill(pid, signal.SIGKILL)
  198. # locate the child in the active or idle list and remove it
  199. # Note: typically, a dead child will be in the active list, since
  200. # exiting children do not send a cleanup status to the controller
  201. try:
  202. self.active_list.pop(self.active_list.index(self.pid_map[pid]))
  203. except:
  204. try:
  205. self.idle_list.pop(self.active_list.index(self.pid_map[pid]))
  206. except:
  207. pass
  208. del self.pid_map[pid]
  209. def spawn_children(self):
  210. ''' Launches up to min_children child processes '''
  211. while self.num_children < self.min_children:
  212. self.spawn_child()
  213. def spawn_child(self, active=False):
  214. ''' Spawns a new child process '''
  215. child = Child(self)
  216. child.read_data, child.write_data = socket.socketpair()
  217. child.pid = os.fork()
  218. sys.stdin.close()
  219. sys.stdin = open(os.devnull, 'r')
  220. sys.stdout.close()
  221. sys.stdout = open(os.devnull, 'w')
  222. sys.stderr.close()
  223. sys.stderr = open(os.devnull, 'w')
  224. if child.pid:
  225. self.num_children += 1
  226. self.pid_map[child.pid] = child
  227. if active:
  228. self.active_list.append(child)
  229. else:
  230. self.idle_list.append(child)
  231. osrf.log.log_internal(
  232. "server: %s spawned child %d : %d total"
  233. % (self.service, child.pid, self.num_children)
  234. )
  235. return child
  236. else:
  237. child.pid = os.getpid()
  238. child.init()
  239. child.run()
  240. osrf.net.get_network_handle().disconnect()
  241. osrf.log.log_internal("server: child exiting...")
  242. os._exit(0)
  243. def register_routers(self):
  244. ''' Registers this application instance with all configured routers '''
  245. routers = osrf.conf.get('routers.router')
  246. if not isinstance(routers, list):
  247. routers = [routers]
  248. for router in routers:
  249. if isinstance(router, dict):
  250. if not 'services' in router or \
  251. self.service in router['services']['service']:
  252. target = "%s@%s/router" % (router['name'], router['domain'])
  253. self.register_router(target)
  254. else:
  255. router_name = osrf.conf.get('router_name')
  256. target = "%s@%s/router" % (router_name, router)
  257. self.register_router(target)
  258. def register_router(self, target):
  259. ''' Registers with a single router '''
  260. osrf.log.log_info("server: registering with router %s" % target)
  261. self.routers.append(target)
  262. reg_msg = osrf.net.NetworkMessage(
  263. recipient = target,
  264. body = 'registering...',
  265. router_command = 'register',
  266. router_class = self.service
  267. )
  268. self.osrf_handle.send(reg_msg)
  269. def cleanup_routers(self):
  270. ''' Un-registers with all connected routers '''
  271. for target in self.routers:
  272. osrf.log.log_info("server: un-registering with router %s" % target)
  273. unreg_msg = osrf.net.NetworkMessage(
  274. recipient = target,
  275. body = 'un-registering...',
  276. router_command = 'unregister',
  277. router_class = self.service
  278. )
  279. self.osrf_handle.send(unreg_msg)
  280. class Child(object):
  281. ''' Models a single child process '''
  282. def __init__(self, controller):
  283. ''' Initializes child process instance '''
  284. # our Controller object
  285. self.controller = controller
  286. # how many requests we've served so far
  287. self.num_requests = 0
  288. # the child reads data from the controller on this socket
  289. self.read_data = None
  290. # the controller sends data to the child on this socket
  291. self.write_data = None
  292. # my process id
  293. self.pid = 0
  294. def run(self):
  295. ''' Loops, processing data, until max_requests is reached '''
  296. while True:
  297. try:
  298. self.read_data.setblocking(1)
  299. data = ''
  300. while True: # read all the data from the socket
  301. buf = None
  302. try:
  303. buf = self.read_data.recv(2048)
  304. except socket.error, exc:
  305. if exc.args[0] == errno.EAGAIN:
  306. break
  307. osrf.log.log_error(
  308. "server: child data read failed: %s" % str(exc)
  309. )
  310. osrf.app.Application.application.child_exit()
  311. return
  312. if buf is None or buf == '':
  313. break
  314. data += buf
  315. self.read_data.setblocking(0)
  316. osrf.log.log_internal("server: child received message: " + data)
  317. osrf.net.get_network_handle().flush_inbound_data()
  318. session = osrf.stack.push(
  319. osrf.net.NetworkMessage.from_xml(data)
  320. )
  321. self.keepalive_loop(session)
  322. self.num_requests += 1
  323. osrf.log.log_internal("server: child done processing message")
  324. if self.num_requests == self.controller.max_requests:
  325. break
  326. # tell the parent we're done w/ this request session
  327. self.send_status()
  328. except KeyboardInterrupt:
  329. pass
  330. # run the exit handler
  331. osrf.app.Application.application.child_exit()
  332. def keepalive_loop(self, session):
  333. '''
  334. Keeps session alive while client is connected.
  335. If timeout occurs, session disconnects and gets cleaned up.
  336. '''
  337. keepalive = self.controller.keepalive
  338. while session.state == osrf.const.OSRF_APP_SESSION_CONNECTED:
  339. status = session.wait(keepalive)
  340. if session.state == osrf.const.OSRF_APP_SESSION_DISCONNECTED:
  341. osrf.log.log_internal(
  342. "server: client sent disconnect, exiting keepalive"
  343. )
  344. break
  345. # if no msg received before keepalive timeout expired
  346. if status is None:
  347. osrf.log.log_info(
  348. "server: no request was received in %d seconds from %s, "
  349. "exiting stateful session"
  350. % (session.remote_id, int(keepalive))
  351. )
  352. session.send_status(
  353. session.thread,
  354. osrf.net_obj.NetworkObject.osrfConnectStatus({
  355. 'status' : 'Disconnected on timeout',
  356. 'statusCode': osrf.const.OSRF_STATUS_TIMEOUT
  357. })
  358. )
  359. break
  360. session.cleanup()
  361. return
  362. def send_status(self):
  363. ''' Informs the controller that we are done processing this request '''
  364. fcntl.lockf(self.controller.write_status.fileno(), fcntl.LOCK_EX)
  365. try:
  366. self.controller.write_status.sendall(str(self.pid).rjust(SIZE_PAD))
  367. finally:
  368. fcntl.lockf(self.controller.write_status.fileno(), fcntl.LOCK_UN)
  369. def init(self):
  370. ''' Connects the opensrf xmpp handle '''
  371. osrf.net.clear_network_handle()
  372. osrf.system.System.net_connect(
  373. resource = '%s_drone' % self.controller.service,
  374. service = self.controller.service
  375. )
  376. osrf.app.Application.application.child_init()