PageRenderTime 71ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/Pyro4/socketserver/threadpoolserver.py

https://github.com/frankladen/Orion-avec-cheveux
Python | 251 lines | 236 code | 2 blank | 13 comment | 0 complexity | 485878df38f0bbb16dab30ad5ed1d3b9 MD5 | raw file
  1. """
  2. Socket server based on a worker thread pool. Doesn't use select.
  3. Uses a single worker thread per client connection.
  4. Pyro - Python Remote Objects. Copyright by Irmen de Jong.
  5. irmen@razorvine.net - http://www.razorvine.net/projects/Pyro
  6. """
  7. from __future__ import with_statement
  8. import socket, logging, sys
  9. try:
  10. import queue
  11. except ImportError:
  12. import Queue as queue
  13. import time, os
  14. from Pyro4 import socketutil, threadutil, errors
  15. import Pyro4
  16. log=logging.getLogger("Pyro.socketserver.threadpool")
  17. class SocketWorker(threadutil.Thread):
  18. """worker thread to process requests"""
  19. def __init__(self, server, daemon):
  20. super(SocketWorker, self).__init__()
  21. self.setDaemon(True)
  22. self.server=server
  23. self.callbackDaemon=daemon
  24. if os.name=="java":
  25. # jython names every thread 'Thread', so we improve that a little
  26. self.setName("Thread-%d"%id(self))
  27. def run(self):
  28. self.running=True
  29. try:
  30. while self.running: # loop over all connections in the queue
  31. self.csock, self.caddr = self.server.workqueue.get()
  32. if self.csock is None and self.caddr is None:
  33. # this was a 'stop' sentinel
  34. self.running=False
  35. break
  36. self.csock=socketutil.SocketConnection(self.csock)
  37. if self.handleConnection(self.csock):
  38. self.server.threadpool.updateWorking(1) # tell the pool we're working
  39. try:
  40. while self.running: # loop over all requests during a single connection
  41. try:
  42. self.callbackDaemon.handleRequest(self.csock)
  43. except (socket.error, errors.ConnectionClosedError):
  44. # client went away.
  45. log.debug("worker %s client disconnected %s", self.getName(), self.caddr)
  46. break
  47. except errors.SecurityError:
  48. log.debug("worker %s client security error %s", self.getName(), self.caddr)
  49. break
  50. self.csock.close()
  51. finally:
  52. # make sure we tell the pool that we are no longer working
  53. self.server.threadpool.updateWorking(-1)
  54. # Note: we don't swallow exceptions here anymore because @Pyro4.callback doesn't
  55. # do anything anymore if we do (the re-raised exception would be swallowed...)
  56. #except Exception:
  57. # exc_type, exc_value, _ = sys.exc_info()
  58. # log.warn("swallow exception in worker %s: %s %s", self.getName(), exc_type, exc_value)
  59. finally:
  60. self.server.threadpool.remove(self)
  61. log.debug("stopping worker %s", self.getName())
  62. def handleConnection(self, conn):
  63. try:
  64. if self.callbackDaemon._handshake(conn):
  65. return True
  66. except (socket.error, errors.PyroError):
  67. x=sys.exc_info()[1]
  68. log.warn("error during connect: %s", x)
  69. conn.close()
  70. return False
  71. class ThreadPool(object):
  72. def __init__(self, server, daemon):
  73. self.lock=threadutil.Lock()
  74. self.pool=set()
  75. self.__server=server
  76. self.__daemon=daemon
  77. self.__working=0
  78. self.__lastshrink=time.time()
  79. def attemptRemove(self, member):
  80. with self.lock:
  81. if len(self.pool)>Pyro4.config.THREADPOOL_MINTHREADS:
  82. self.pool.remove(member)
  83. return True
  84. return False
  85. def remove(self, member):
  86. with self.lock:
  87. try:
  88. self.pool.remove(member)
  89. except KeyError:
  90. pass
  91. def attemptSpawn(self):
  92. with self.lock:
  93. if len(self.pool)<Pyro4.config.THREADPOOL_MAXTHREADS:
  94. worker=SocketWorker(self.__server, self.__daemon)
  95. self.pool.add(worker)
  96. worker.start()
  97. return True
  98. return False
  99. def poolCritical(self):
  100. idle=len(self.pool)-self.__working
  101. return idle<=0
  102. def updateWorking(self, number):
  103. self.shrink()
  104. with self.lock:
  105. self.__working+=number
  106. def shrink(self):
  107. threads=len(self.pool)
  108. if threads>Pyro4.config.THREADPOOL_MINTHREADS:
  109. idle=threads-self.__working
  110. if idle>Pyro4.config.THREADPOOL_MINTHREADS and (time.time()-self.__lastshrink)>Pyro4.config.THREADPOOL_IDLETIMEOUT:
  111. for _ in range(idle-Pyro4.config.THREADPOOL_MINTHREADS):
  112. self.__server.workqueue.put((None, None)) # put a 'stop' sentinel in the worker queue to kill a worker
  113. self.__lastshrink=time.time()
  114. class SocketServer_Threadpool(object):
  115. """transport server for socket connections, worker thread pool version."""
  116. def init(self, daemon, host, port, unixsocket=None):
  117. log.info("starting thread pool socketserver")
  118. self.sock=None
  119. bind_location=unixsocket if unixsocket else (host,port)
  120. self.sock=socketutil.createSocket(bind=bind_location, reuseaddr=Pyro4.config.SOCK_REUSE, timeout=Pyro4.config.COMMTIMEOUT, noinherit=True)
  121. self._socketaddr=self.sock.getsockname()
  122. if self._socketaddr[0].startswith("127."):
  123. if host is None or host.lower()!="localhost" and not host.startswith("127."):
  124. log.warn("weird DNS setup: %s resolves to localhost (127.x.x.x)", host)
  125. if unixsocket:
  126. self.locationStr="./u:"+unixsocket
  127. else:
  128. host=host or self._socketaddr[0]
  129. port=port or self._socketaddr[1]
  130. self.locationStr="%s:%d" % (host, port)
  131. self.threadpool=ThreadPool(self, daemon)
  132. self.workqueue=queue.Queue()
  133. for _ in range(Pyro4.config.THREADPOOL_MINTHREADS):
  134. self.threadpool.attemptSpawn()
  135. log.info("%d worker threads started", len(self.threadpool.pool))
  136. def __del__(self):
  137. if self.sock is not None:
  138. self.sock.close()
  139. def __repr__(self):
  140. return "<%s on %s, poolsize %d, %d queued>" % (self.__class__.__name__, self.locationStr,
  141. len(self.threadpool.pool), self.workqueue.qsize())
  142. def loop(self, loopCondition=lambda: True):
  143. log.debug("threadpool server requestloop")
  144. while (self.sock is not None) and loopCondition():
  145. try:
  146. self.events([self.sock])
  147. except socket.error:
  148. x=sys.exc_info()[1]
  149. err=getattr(x, "errno", x.args[0])
  150. if not loopCondition():
  151. # swallow the socket error if loop terminates anyway
  152. # this can occur if we are asked to shutdown, socket can be invalid then
  153. break
  154. if err in socketutil.ERRNO_RETRIES:
  155. continue
  156. else:
  157. raise
  158. except KeyboardInterrupt:
  159. log.debug("stopping on break signal")
  160. break
  161. log.debug("threadpool server exits requestloop")
  162. def events(self, eventsockets):
  163. """used for external event loops: handle events that occur on one of the sockets of this server"""
  164. # we only react on events on our own server socket.
  165. # all other (client) sockets are owned by their individual threads.
  166. assert self.sock in eventsockets
  167. try:
  168. csock, caddr=self.sock.accept()
  169. log.debug("connection from %s", caddr)
  170. if Pyro4.config.COMMTIMEOUT:
  171. csock.settimeout(Pyro4.config.COMMTIMEOUT)
  172. if self.threadpool.poolCritical():
  173. self.threadpool.attemptSpawn()
  174. self.workqueue.put((csock, caddr))
  175. except socket.timeout:
  176. pass # just continue the loop on a timeout on accept
  177. def close(self, joinWorkers=True):
  178. log.debug("closing threadpool server")
  179. if self.sock:
  180. sockname=None
  181. try:
  182. sockname=self.sock.getsockname()
  183. except socket.error:
  184. pass
  185. try:
  186. self.sock.close()
  187. if type(sockname) is str:
  188. # it was a Unix domain socket, remove it from the filesystem
  189. if os.path.exists(sockname):
  190. os.remove(sockname)
  191. except Exception:
  192. pass
  193. self.sock=None
  194. for worker in self.threadpool.pool.copy():
  195. worker.running=False
  196. csock=getattr(worker, "csock", None)
  197. if csock:
  198. try:
  199. csock.sock.shutdown(socket.SHUT_RDWR)
  200. except (EnvironmentError, socket.error):
  201. pass
  202. csock.close()
  203. if self.workqueue is not None:
  204. self.workqueue.put((None, None)) # put a 'stop' sentinel in the worker queue
  205. while joinWorkers:
  206. try:
  207. worker=self.threadpool.pool.pop()
  208. except KeyError:
  209. break
  210. else:
  211. worker.join()
  212. @property
  213. def sockets(self):
  214. # the server socket is all we care about, all client sockets are running in their own threads
  215. return [self.sock]
  216. def wakeup(self):
  217. """bit of a hack to trigger a blocking server to get out of the loop, useful at clean shutdowns"""
  218. try:
  219. sock=socketutil.createSocket(connect=self._socketaddr)
  220. if sys.version_info<(3, 0):
  221. sock.send("!"*16)
  222. else:
  223. sock.send(bytes([1]*16))
  224. sock.close()
  225. except socket.error:
  226. pass