managers.py 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225
  1. #
  2. # Module providing the `SyncManager` class for dealing
  3. # with shared objects
  4. #
  5. # multiprocessing/managers.py
  6. #
  7. # Copyright (c) 2006-2008, R Oudkerk
  8. # Licensed to PSF under a Contributor Agreement.
  9. #
  10. __all__ = [ 'BaseManager', 'SyncManager', 'BaseProxy', 'Token' ]
  11. #
  12. # Imports
  13. #
  14. import sys
  15. import threading
  16. import array
  17. import queue
  18. import time
  19. from traceback import format_exc
  20. from . import connection
  21. from .context import reduction, get_spawning_popen, ProcessError
  22. from . import pool
  23. from . import process
  24. from . import util
  25. from . import get_context
  26. #
  27. # Register some things for pickling
  28. #
  29. def reduce_array(a):
  30. return array.array, (a.typecode, a.tobytes())
  31. reduction.register(array.array, reduce_array)
  32. view_types = [type(getattr({}, name)()) for name in ('items','keys','values')]
  33. if view_types[0] is not list: # only needed in Py3.0
  34. def rebuild_as_list(obj):
  35. return list, (list(obj),)
  36. for view_type in view_types:
  37. reduction.register(view_type, rebuild_as_list)
  38. #
  39. # Type for identifying shared objects
  40. #
  41. class Token(object):
  42. '''
  43. Type to uniquely indentify a shared object
  44. '''
  45. __slots__ = ('typeid', 'address', 'id')
  46. def __init__(self, typeid, address, id):
  47. (self.typeid, self.address, self.id) = (typeid, address, id)
  48. def __getstate__(self):
  49. return (self.typeid, self.address, self.id)
  50. def __setstate__(self, state):
  51. (self.typeid, self.address, self.id) = state
  52. def __repr__(self):
  53. return '%s(typeid=%r, address=%r, id=%r)' % \
  54. (self.__class__.__name__, self.typeid, self.address, self.id)
  55. #
  56. # Function for communication with a manager's server process
  57. #
  58. def dispatch(c, id, methodname, args=(), kwds={}):
  59. '''
  60. Send a message to manager using connection `c` and return response
  61. '''
  62. c.send((id, methodname, args, kwds))
  63. kind, result = c.recv()
  64. if kind == '#RETURN':
  65. return result
  66. raise convert_to_error(kind, result)
  67. def convert_to_error(kind, result):
  68. if kind == '#ERROR':
  69. return result
  70. elif kind in ('#TRACEBACK', '#UNSERIALIZABLE'):
  71. if not isinstance(result, str):
  72. raise TypeError(
  73. "Result {0!r} (kind '{1}') type is {2}, not str".format(
  74. result, kind, type(result)))
  75. if kind == '#UNSERIALIZABLE':
  76. return RemoteError('Unserializable message: %s\n' % result)
  77. else:
  78. return RemoteError(result)
  79. else:
  80. return ValueError('Unrecognized message type {!r}'.format(kind))
  81. class RemoteError(Exception):
  82. def __str__(self):
  83. return ('\n' + '-'*75 + '\n' + str(self.args[0]) + '-'*75)
  84. #
  85. # Functions for finding the method names of an object
  86. #
  87. def all_methods(obj):
  88. '''
  89. Return a list of names of methods of `obj`
  90. '''
  91. temp = []
  92. for name in dir(obj):
  93. func = getattr(obj, name)
  94. if callable(func):
  95. temp.append(name)
  96. return temp
  97. def public_methods(obj):
  98. '''
  99. Return a list of names of methods of `obj` which do not start with '_'
  100. '''
  101. return [name for name in all_methods(obj) if name[0] != '_']
  102. #
  103. # Server which is run in a process controlled by a manager
  104. #
  105. class Server(object):
  106. '''
  107. Server class which runs in a process controlled by a manager object
  108. '''
  109. public = ['shutdown', 'create', 'accept_connection', 'get_methods',
  110. 'debug_info', 'number_of_objects', 'dummy', 'incref', 'decref']
  111. def __init__(self, registry, address, authkey, serializer):
  112. if not isinstance(authkey, bytes):
  113. raise TypeError(
  114. "Authkey {0!r} is type {1!s}, not bytes".format(
  115. authkey, type(authkey)))
  116. self.registry = registry
  117. self.authkey = process.AuthenticationString(authkey)
  118. Listener, Client = listener_client[serializer]
  119. # do authentication later
  120. self.listener = Listener(address=address, backlog=16)
  121. self.address = self.listener.address
  122. self.id_to_obj = {'0': (None, ())}
  123. self.id_to_refcount = {}
  124. self.id_to_local_proxy_obj = {}
  125. self.mutex = threading.Lock()
  126. def serve_forever(self):
  127. '''
  128. Run the server forever
  129. '''
  130. self.stop_event = threading.Event()
  131. process.current_process()._manager_server = self
  132. try:
  133. accepter = threading.Thread(target=self.accepter)
  134. accepter.daemon = True
  135. accepter.start()
  136. try:
  137. while not self.stop_event.is_set():
  138. self.stop_event.wait(1)
  139. except (KeyboardInterrupt, SystemExit):
  140. pass
  141. finally:
  142. if sys.stdout != sys.__stdout__: # what about stderr?
  143. util.debug('resetting stdout, stderr')
  144. sys.stdout = sys.__stdout__
  145. sys.stderr = sys.__stderr__
  146. sys.exit(0)
  147. def accepter(self):
  148. while True:
  149. try:
  150. c = self.listener.accept()
  151. except OSError:
  152. continue
  153. t = threading.Thread(target=self.handle_request, args=(c,))
  154. t.daemon = True
  155. t.start()
  156. def handle_request(self, c):
  157. '''
  158. Handle a new connection
  159. '''
  160. funcname = result = request = None
  161. try:
  162. connection.deliver_challenge(c, self.authkey)
  163. connection.answer_challenge(c, self.authkey)
  164. request = c.recv()
  165. ignore, funcname, args, kwds = request
  166. assert funcname in self.public, '%r unrecognized' % funcname
  167. func = getattr(self, funcname)
  168. except Exception:
  169. msg = ('#TRACEBACK', format_exc())
  170. else:
  171. try:
  172. result = func(c, *args, **kwds)
  173. except Exception:
  174. msg = ('#TRACEBACK', format_exc())
  175. else:
  176. msg = ('#RETURN', result)
  177. try:
  178. c.send(msg)
  179. except Exception as e:
  180. try:
  181. c.send(('#TRACEBACK', format_exc()))
  182. except Exception:
  183. pass
  184. util.info('Failure to send message: %r', msg)
  185. util.info(' ... request was %r', request)
  186. util.info(' ... exception was %r', e)
  187. c.close()
  188. def serve_client(self, conn):
  189. '''
  190. Handle requests from the proxies in a particular process/thread
  191. '''
  192. util.debug('starting server thread to service %r',
  193. threading.current_thread().name)
  194. recv = conn.recv
  195. send = conn.send
  196. id_to_obj = self.id_to_obj
  197. while not self.stop_event.is_set():
  198. try:
  199. methodname = obj = None
  200. request = recv()
  201. ident, methodname, args, kwds = request
  202. try:
  203. obj, exposed, gettypeid = id_to_obj[ident]
  204. except KeyError as ke:
  205. try:
  206. obj, exposed, gettypeid = \
  207. self.id_to_local_proxy_obj[ident]
  208. except KeyError as second_ke:
  209. raise ke
  210. if methodname not in exposed:
  211. raise AttributeError(
  212. 'method %r of %r object is not in exposed=%r' %
  213. (methodname, type(obj), exposed)
  214. )
  215. function = getattr(obj, methodname)
  216. try:
  217. res = function(*args, **kwds)
  218. except Exception as e:
  219. msg = ('#ERROR', e)
  220. else:
  221. typeid = gettypeid and gettypeid.get(methodname, None)
  222. if typeid:
  223. rident, rexposed = self.create(conn, typeid, res)
  224. token = Token(typeid, self.address, rident)
  225. msg = ('#PROXY', (rexposed, token))
  226. else:
  227. msg = ('#RETURN', res)
  228. except AttributeError:
  229. if methodname is None:
  230. msg = ('#TRACEBACK', format_exc())
  231. else:
  232. try:
  233. fallback_func = self.fallback_mapping[methodname]
  234. result = fallback_func(
  235. self, conn, ident, obj, *args, **kwds
  236. )
  237. msg = ('#RETURN', result)
  238. except Exception:
  239. msg = ('#TRACEBACK', format_exc())
  240. except EOFError:
  241. util.debug('got EOF -- exiting thread serving %r',
  242. threading.current_thread().name)
  243. sys.exit(0)
  244. except Exception:
  245. msg = ('#TRACEBACK', format_exc())
  246. try:
  247. try:
  248. send(msg)
  249. except Exception as e:
  250. send(('#UNSERIALIZABLE', format_exc()))
  251. except Exception as e:
  252. util.info('exception in thread serving %r',
  253. threading.current_thread().name)
  254. util.info(' ... message was %r', msg)
  255. util.info(' ... exception was %r', e)
  256. conn.close()
  257. sys.exit(1)
  258. def fallback_getvalue(self, conn, ident, obj):
  259. return obj
  260. def fallback_str(self, conn, ident, obj):
  261. return str(obj)
  262. def fallback_repr(self, conn, ident, obj):
  263. return repr(obj)
  264. fallback_mapping = {
  265. '__str__':fallback_str,
  266. '__repr__':fallback_repr,
  267. '#GETVALUE':fallback_getvalue
  268. }
  269. def dummy(self, c):
  270. pass
  271. def debug_info(self, c):
  272. '''
  273. Return some info --- useful to spot problems with refcounting
  274. '''
  275. # Perhaps include debug info about 'c'?
  276. with self.mutex:
  277. result = []
  278. keys = list(self.id_to_refcount.keys())
  279. keys.sort()
  280. for ident in keys:
  281. if ident != '0':
  282. result.append(' %s: refcount=%s\n %s' %
  283. (ident, self.id_to_refcount[ident],
  284. str(self.id_to_obj[ident][0])[:75]))
  285. return '\n'.join(result)
  286. def number_of_objects(self, c):
  287. '''
  288. Number of shared objects
  289. '''
  290. # Doesn't use (len(self.id_to_obj) - 1) as we shouldn't count ident='0'
  291. return len(self.id_to_refcount)
  292. def shutdown(self, c):
  293. '''
  294. Shutdown this process
  295. '''
  296. try:
  297. util.debug('manager received shutdown message')
  298. c.send(('#RETURN', None))
  299. except:
  300. import traceback
  301. traceback.print_exc()
  302. finally:
  303. self.stop_event.set()
  304. def create(*args, **kwds):
  305. '''
  306. Create a new shared object and return its id
  307. '''
  308. if len(args) >= 3:
  309. self, c, typeid, *args = args
  310. elif not args:
  311. raise TypeError("descriptor 'create' of 'Server' object "
  312. "needs an argument")
  313. else:
  314. if 'typeid' not in kwds:
  315. raise TypeError('create expected at least 2 positional '
  316. 'arguments, got %d' % (len(args)-1))
  317. typeid = kwds.pop('typeid')
  318. if len(args) >= 2:
  319. self, c, *args = args
  320. else:
  321. if 'c' not in kwds:
  322. raise TypeError('create expected at least 2 positional '
  323. 'arguments, got %d' % (len(args)-1))
  324. c = kwds.pop('c')
  325. self, *args = args
  326. args = tuple(args)
  327. with self.mutex:
  328. callable, exposed, method_to_typeid, proxytype = \
  329. self.registry[typeid]
  330. if callable is None:
  331. if kwds or (len(args) != 1):
  332. raise ValueError(
  333. "Without callable, must have one non-keyword argument")
  334. obj = args[0]
  335. else:
  336. obj = callable(*args, **kwds)
  337. if exposed is None:
  338. exposed = public_methods(obj)
  339. if method_to_typeid is not None:
  340. if not isinstance(method_to_typeid, dict):
  341. raise TypeError(
  342. "Method_to_typeid {0!r}: type {1!s}, not dict".format(
  343. method_to_typeid, type(method_to_typeid)))
  344. exposed = list(exposed) + list(method_to_typeid)
  345. ident = '%x' % id(obj) # convert to string because xmlrpclib
  346. # only has 32 bit signed integers
  347. util.debug('%r callable returned object with id %r', typeid, ident)
  348. self.id_to_obj[ident] = (obj, set(exposed), method_to_typeid)
  349. if ident not in self.id_to_refcount:
  350. self.id_to_refcount[ident] = 0
  351. self.incref(c, ident)
  352. return ident, tuple(exposed)
  353. def get_methods(self, c, token):
  354. '''
  355. Return the methods of the shared object indicated by token
  356. '''
  357. return tuple(self.id_to_obj[token.id][1])
  358. def accept_connection(self, c, name):
  359. '''
  360. Spawn a new thread to serve this connection
  361. '''
  362. threading.current_thread().name = name
  363. c.send(('#RETURN', None))
  364. self.serve_client(c)
  365. def incref(self, c, ident):
  366. with self.mutex:
  367. try:
  368. self.id_to_refcount[ident] += 1
  369. except KeyError as ke:
  370. # If no external references exist but an internal (to the
  371. # manager) still does and a new external reference is created
  372. # from it, restore the manager's tracking of it from the
  373. # previously stashed internal ref.
  374. if ident in self.id_to_local_proxy_obj:
  375. self.id_to_refcount[ident] = 1
  376. self.id_to_obj[ident] = \
  377. self.id_to_local_proxy_obj[ident]
  378. obj, exposed, gettypeid = self.id_to_obj[ident]
  379. util.debug('Server re-enabled tracking & INCREF %r', ident)
  380. else:
  381. raise ke
  382. def decref(self, c, ident):
  383. if ident not in self.id_to_refcount and \
  384. ident in self.id_to_local_proxy_obj:
  385. util.debug('Server DECREF skipping %r', ident)
  386. return
  387. with self.mutex:
  388. if self.id_to_refcount[ident] <= 0:
  389. raise AssertionError(
  390. "Id {0!s} ({1!r}) has refcount {2:n}, not 1+".format(
  391. ident, self.id_to_obj[ident],
  392. self.id_to_refcount[ident]))
  393. self.id_to_refcount[ident] -= 1
  394. if self.id_to_refcount[ident] == 0:
  395. del self.id_to_refcount[ident]
  396. if ident not in self.id_to_refcount:
  397. # Two-step process in case the object turns out to contain other
  398. # proxy objects (e.g. a managed list of managed lists).
  399. # Otherwise, deleting self.id_to_obj[ident] would trigger the
  400. # deleting of the stored value (another managed object) which would
  401. # in turn attempt to acquire the mutex that is already held here.
  402. self.id_to_obj[ident] = (None, (), None) # thread-safe
  403. util.debug('disposing of obj with id %r', ident)
  404. with self.mutex:
  405. del self.id_to_obj[ident]
  406. #
  407. # Class to represent state of a manager
  408. #
  409. class State(object):
  410. __slots__ = ['value']
  411. INITIAL = 0
  412. STARTED = 1
  413. SHUTDOWN = 2
  414. #
  415. # Mapping from serializer name to Listener and Client types
  416. #
  417. listener_client = {
  418. 'pickle' : (connection.Listener, connection.Client),
  419. 'xmlrpclib' : (connection.XmlListener, connection.XmlClient)
  420. }
  421. #
  422. # Definition of BaseManager
  423. #
  424. class BaseManager(object):
  425. '''
  426. Base class for managers
  427. '''
  428. _registry = {}
  429. _Server = Server
  430. def __init__(self, address=None, authkey=None, serializer='pickle',
  431. ctx=None):
  432. if authkey is None:
  433. authkey = process.current_process().authkey
  434. self._address = address # XXX not final address if eg ('', 0)
  435. self._authkey = process.AuthenticationString(authkey)
  436. self._state = State()
  437. self._state.value = State.INITIAL
  438. self._serializer = serializer
  439. self._Listener, self._Client = listener_client[serializer]
  440. self._ctx = ctx or get_context()
  441. def get_server(self):
  442. '''
  443. Return server object with serve_forever() method and address attribute
  444. '''
  445. if self._state.value != State.INITIAL:
  446. if self._state.value == State.STARTED:
  447. raise ProcessError("Already started server")
  448. elif self._state.value == State.SHUTDOWN:
  449. raise ProcessError("Manager has shut down")
  450. else:
  451. raise ProcessError(
  452. "Unknown state {!r}".format(self._state.value))
  453. return Server(self._registry, self._address,
  454. self._authkey, self._serializer)
  455. def connect(self):
  456. '''
  457. Connect manager object to the server process
  458. '''
  459. Listener, Client = listener_client[self._serializer]
  460. conn = Client(self._address, authkey=self._authkey)
  461. dispatch(conn, None, 'dummy')
  462. self._state.value = State.STARTED
  463. def start(self, initializer=None, initargs=()):
  464. '''
  465. Spawn a server process for this manager object
  466. '''
  467. if self._state.value != State.INITIAL:
  468. if self._state.value == State.STARTED:
  469. raise ProcessError("Already started server")
  470. elif self._state.value == State.SHUTDOWN:
  471. raise ProcessError("Manager has shut down")
  472. else:
  473. raise ProcessError(
  474. "Unknown state {!r}".format(self._state.value))
  475. if initializer is not None and not callable(initializer):
  476. raise TypeError('initializer must be a callable')
  477. # pipe over which we will retrieve address of server
  478. reader, writer = connection.Pipe(duplex=False)
  479. # spawn process which runs a server
  480. self._process = self._ctx.Process(
  481. target=type(self)._run_server,
  482. args=(self._registry, self._address, self._authkey,
  483. self._serializer, writer, initializer, initargs),
  484. )
  485. ident = ':'.join(str(i) for i in self._process._identity)
  486. self._process.name = type(self).__name__ + '-' + ident
  487. self._process.start()
  488. # get address of server
  489. writer.close()
  490. self._address = reader.recv()
  491. reader.close()
  492. # register a finalizer
  493. self._state.value = State.STARTED
  494. self.shutdown = util.Finalize(
  495. self, type(self)._finalize_manager,
  496. args=(self._process, self._address, self._authkey,
  497. self._state, self._Client),
  498. exitpriority=0
  499. )
  500. @classmethod
  501. def _run_server(cls, registry, address, authkey, serializer, writer,
  502. initializer=None, initargs=()):
  503. '''
  504. Create a server, report its address and run it
  505. '''
  506. if initializer is not None:
  507. initializer(*initargs)
  508. # create server
  509. server = cls._Server(registry, address, authkey, serializer)
  510. # inform parent process of the server's address
  511. writer.send(server.address)
  512. writer.close()
  513. # run the manager
  514. util.info('manager serving at %r', server.address)
  515. server.serve_forever()
  516. def _create(*args, **kwds):
  517. '''
  518. Create a new shared object; return the token and exposed tuple
  519. '''
  520. self, typeid, *args = args
  521. args = tuple(args)
  522. assert self._state.value == State.STARTED, 'server not yet started'
  523. conn = self._Client(self._address, authkey=self._authkey)
  524. try:
  525. id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds)
  526. finally:
  527. conn.close()
  528. return Token(typeid, self._address, id), exposed
  529. def join(self, timeout=None):
  530. '''
  531. Join the manager process (if it has been spawned)
  532. '''
  533. if self._process is not None:
  534. self._process.join(timeout)
  535. if not self._process.is_alive():
  536. self._process = None
  537. def _debug_info(self):
  538. '''
  539. Return some info about the servers shared objects and connections
  540. '''
  541. conn = self._Client(self._address, authkey=self._authkey)
  542. try:
  543. return dispatch(conn, None, 'debug_info')
  544. finally:
  545. conn.close()
  546. def _number_of_objects(self):
  547. '''
  548. Return the number of shared objects
  549. '''
  550. conn = self._Client(self._address, authkey=self._authkey)
  551. try:
  552. return dispatch(conn, None, 'number_of_objects')
  553. finally:
  554. conn.close()
  555. def __enter__(self):
  556. if self._state.value == State.INITIAL:
  557. self.start()
  558. if self._state.value != State.STARTED:
  559. if self._state.value == State.INITIAL:
  560. raise ProcessError("Unable to start server")
  561. elif self._state.value == State.SHUTDOWN:
  562. raise ProcessError("Manager has shut down")
  563. else:
  564. raise ProcessError(
  565. "Unknown state {!r}".format(self._state.value))
  566. return self
  567. def __exit__(self, exc_type, exc_val, exc_tb):
  568. self.shutdown()
  569. @staticmethod
  570. def _finalize_manager(process, address, authkey, state, _Client):
  571. '''
  572. Shutdown the manager process; will be registered as a finalizer
  573. '''
  574. if process.is_alive():
  575. util.info('sending shutdown message to manager')
  576. try:
  577. conn = _Client(address, authkey=authkey)
  578. try:
  579. dispatch(conn, None, 'shutdown')
  580. finally:
  581. conn.close()
  582. except Exception:
  583. pass
  584. process.join(timeout=1.0)
  585. if process.is_alive():
  586. util.info('manager still alive')
  587. if hasattr(process, 'terminate'):
  588. util.info('trying to `terminate()` manager process')
  589. process.terminate()
  590. process.join(timeout=0.1)
  591. if process.is_alive():
  592. util.info('manager still alive after terminate')
  593. state.value = State.SHUTDOWN
  594. try:
  595. del BaseProxy._address_to_local[address]
  596. except KeyError:
  597. pass
  598. @property
  599. def address(self):
  600. return self._address
  601. @classmethod
  602. def register(cls, typeid, callable=None, proxytype=None, exposed=None,
  603. method_to_typeid=None, create_method=True):
  604. '''
  605. Register a typeid with the manager type
  606. '''
  607. if '_registry' not in cls.__dict__:
  608. cls._registry = cls._registry.copy()
  609. if proxytype is None:
  610. proxytype = AutoProxy
  611. exposed = exposed or getattr(proxytype, '_exposed_', None)
  612. method_to_typeid = method_to_typeid or \
  613. getattr(proxytype, '_method_to_typeid_', None)
  614. if method_to_typeid:
  615. for key, value in list(method_to_typeid.items()): # isinstance?
  616. assert type(key) is str, '%r is not a string' % key
  617. assert type(value) is str, '%r is not a string' % value
  618. cls._registry[typeid] = (
  619. callable, exposed, method_to_typeid, proxytype
  620. )
  621. if create_method:
  622. def temp(self, *args, **kwds):
  623. util.debug('requesting creation of a shared %r object', typeid)
  624. token, exp = self._create(typeid, *args, **kwds)
  625. proxy = proxytype(
  626. token, self._serializer, manager=self,
  627. authkey=self._authkey, exposed=exp
  628. )
  629. conn = self._Client(token.address, authkey=self._authkey)
  630. dispatch(conn, None, 'decref', (token.id,))
  631. return proxy
  632. temp.__name__ = typeid
  633. setattr(cls, typeid, temp)
  634. #
  635. # Subclass of set which get cleared after a fork
  636. #
  637. class ProcessLocalSet(set):
  638. def __init__(self):
  639. util.register_after_fork(self, lambda obj: obj.clear())
  640. def __reduce__(self):
  641. return type(self), ()
  642. #
  643. # Definition of BaseProxy
  644. #
  645. class BaseProxy(object):
  646. '''
  647. A base for proxies of shared objects
  648. '''
  649. _address_to_local = {}
  650. _mutex = util.ForkAwareThreadLock()
  651. def __init__(self, token, serializer, manager=None,
  652. authkey=None, exposed=None, incref=True, manager_owned=False):
  653. with BaseProxy._mutex:
  654. tls_idset = BaseProxy._address_to_local.get(token.address, None)
  655. if tls_idset is None:
  656. tls_idset = util.ForkAwareLocal(), ProcessLocalSet()
  657. BaseProxy._address_to_local[token.address] = tls_idset
  658. # self._tls is used to record the connection used by this
  659. # thread to communicate with the manager at token.address
  660. self._tls = tls_idset[0]
  661. # self._idset is used to record the identities of all shared
  662. # objects for which the current process owns references and
  663. # which are in the manager at token.address
  664. self._idset = tls_idset[1]
  665. self._token = token
  666. self._id = self._token.id
  667. self._manager = manager
  668. self._serializer = serializer
  669. self._Client = listener_client[serializer][1]
  670. # Should be set to True only when a proxy object is being created
  671. # on the manager server; primary use case: nested proxy objects.
  672. # RebuildProxy detects when a proxy is being created on the manager
  673. # and sets this value appropriately.
  674. self._owned_by_manager = manager_owned
  675. if authkey is not None:
  676. self._authkey = process.AuthenticationString(authkey)
  677. elif self._manager is not None:
  678. self._authkey = self._manager._authkey
  679. else:
  680. self._authkey = process.current_process().authkey
  681. if incref:
  682. self._incref()
  683. util.register_after_fork(self, BaseProxy._after_fork)
  684. def _connect(self):
  685. util.debug('making connection to manager')
  686. name = process.current_process().name
  687. if threading.current_thread().name != 'MainThread':
  688. name += '|' + threading.current_thread().name
  689. conn = self._Client(self._token.address, authkey=self._authkey)
  690. dispatch(conn, None, 'accept_connection', (name,))
  691. self._tls.connection = conn
  692. def _callmethod(self, methodname, args=(), kwds={}):
  693. '''
  694. Try to call a method of the referrent and return a copy of the result
  695. '''
  696. try:
  697. conn = self._tls.connection
  698. except AttributeError:
  699. util.debug('thread %r does not own a connection',
  700. threading.current_thread().name)
  701. self._connect()
  702. conn = self._tls.connection
  703. conn.send((self._id, methodname, args, kwds))
  704. kind, result = conn.recv()
  705. if kind == '#RETURN':
  706. return result
  707. elif kind == '#PROXY':
  708. exposed, token = result
  709. proxytype = self._manager._registry[token.typeid][-1]
  710. token.address = self._token.address
  711. proxy = proxytype(
  712. token, self._serializer, manager=self._manager,
  713. authkey=self._authkey, exposed=exposed
  714. )
  715. conn = self._Client(token.address, authkey=self._authkey)
  716. dispatch(conn, None, 'decref', (token.id,))
  717. return proxy
  718. raise convert_to_error(kind, result)
  719. def _getvalue(self):
  720. '''
  721. Get a copy of the value of the referent
  722. '''
  723. return self._callmethod('#GETVALUE')
  724. def _incref(self):
  725. if self._owned_by_manager:
  726. util.debug('owned_by_manager skipped INCREF of %r', self._token.id)
  727. return
  728. conn = self._Client(self._token.address, authkey=self._authkey)
  729. dispatch(conn, None, 'incref', (self._id,))
  730. util.debug('INCREF %r', self._token.id)
  731. self._idset.add(self._id)
  732. state = self._manager and self._manager._state
  733. self._close = util.Finalize(
  734. self, BaseProxy._decref,
  735. args=(self._token, self._authkey, state,
  736. self._tls, self._idset, self._Client),
  737. exitpriority=10
  738. )
  739. @staticmethod
  740. def _decref(token, authkey, state, tls, idset, _Client):
  741. idset.discard(token.id)
  742. # check whether manager is still alive
  743. if state is None or state.value == State.STARTED:
  744. # tell manager this process no longer cares about referent
  745. try:
  746. util.debug('DECREF %r', token.id)
  747. conn = _Client(token.address, authkey=authkey)
  748. dispatch(conn, None, 'decref', (token.id,))
  749. except Exception as e:
  750. util.debug('... decref failed %s', e)
  751. else:
  752. util.debug('DECREF %r -- manager already shutdown', token.id)
  753. # check whether we can close this thread's connection because
  754. # the process owns no more references to objects for this manager
  755. if not idset and hasattr(tls, 'connection'):
  756. util.debug('thread %r has no more proxies so closing conn',
  757. threading.current_thread().name)
  758. tls.connection.close()
  759. del tls.connection
  760. def _after_fork(self):
  761. self._manager = None
  762. try:
  763. self._incref()
  764. except Exception as e:
  765. # the proxy may just be for a manager which has shutdown
  766. util.info('incref failed: %s' % e)
  767. def __reduce__(self):
  768. kwds = {}
  769. if get_spawning_popen() is not None:
  770. kwds['authkey'] = self._authkey
  771. if getattr(self, '_isauto', False):
  772. kwds['exposed'] = self._exposed_
  773. return (RebuildProxy,
  774. (AutoProxy, self._token, self._serializer, kwds))
  775. else:
  776. return (RebuildProxy,
  777. (type(self), self._token, self._serializer, kwds))
  778. def __deepcopy__(self, memo):
  779. return self._getvalue()
  780. def __repr__(self):
  781. return '<%s object, typeid %r at %#x>' % \
  782. (type(self).__name__, self._token.typeid, id(self))
  783. def __str__(self):
  784. '''
  785. Return representation of the referent (or a fall-back if that fails)
  786. '''
  787. try:
  788. return self._callmethod('__repr__')
  789. except Exception:
  790. return repr(self)[:-1] + "; '__str__()' failed>"
  791. #
  792. # Function used for unpickling
  793. #
  794. def RebuildProxy(func, token, serializer, kwds):
  795. '''
  796. Function used for unpickling proxy objects.
  797. '''
  798. server = getattr(process.current_process(), '_manager_server', None)
  799. if server and server.address == token.address:
  800. util.debug('Rebuild a proxy owned by manager, token=%r', token)
  801. kwds['manager_owned'] = True
  802. if token.id not in server.id_to_local_proxy_obj:
  803. server.id_to_local_proxy_obj[token.id] = \
  804. server.id_to_obj[token.id]
  805. incref = (
  806. kwds.pop('incref', True) and
  807. not getattr(process.current_process(), '_inheriting', False)
  808. )
  809. return func(token, serializer, incref=incref, **kwds)
  810. #
  811. # Functions to create proxies and proxy types
  812. #
  813. def MakeProxyType(name, exposed, _cache={}):
  814. '''
  815. Return a proxy type whose methods are given by `exposed`
  816. '''
  817. exposed = tuple(exposed)
  818. try:
  819. return _cache[(name, exposed)]
  820. except KeyError:
  821. pass
  822. dic = {}
  823. for meth in exposed:
  824. exec('''def %s(self, *args, **kwds):
  825. return self._callmethod(%r, args, kwds)''' % (meth, meth), dic)
  826. ProxyType = type(name, (BaseProxy,), dic)
  827. ProxyType._exposed_ = exposed
  828. _cache[(name, exposed)] = ProxyType
  829. return ProxyType
  830. def AutoProxy(token, serializer, manager=None, authkey=None,
  831. exposed=None, incref=True):
  832. '''
  833. Return an auto-proxy for `token`
  834. '''
  835. _Client = listener_client[serializer][1]
  836. if exposed is None:
  837. conn = _Client(token.address, authkey=authkey)
  838. try:
  839. exposed = dispatch(conn, None, 'get_methods', (token,))
  840. finally:
  841. conn.close()
  842. if authkey is None and manager is not None:
  843. authkey = manager._authkey
  844. if authkey is None:
  845. authkey = process.current_process().authkey
  846. ProxyType = MakeProxyType('AutoProxy[%s]' % token.typeid, exposed)
  847. proxy = ProxyType(token, serializer, manager=manager, authkey=authkey,
  848. incref=incref)
  849. proxy._isauto = True
  850. return proxy
  851. #
  852. # Types/callables which we will register with SyncManager
  853. #
  854. class Namespace(object):
  855. def __init__(self, **kwds):
  856. self.__dict__.update(kwds)
  857. def __repr__(self):
  858. items = list(self.__dict__.items())
  859. temp = []
  860. for name, value in items:
  861. if not name.startswith('_'):
  862. temp.append('%s=%r' % (name, value))
  863. temp.sort()
  864. return '%s(%s)' % (self.__class__.__name__, ', '.join(temp))
  865. class Value(object):
  866. def __init__(self, typecode, value, lock=True):
  867. self._typecode = typecode
  868. self._value = value
  869. def get(self):
  870. return self._value
  871. def set(self, value):
  872. self._value = value
  873. def __repr__(self):
  874. return '%s(%r, %r)'%(type(self).__name__, self._typecode, self._value)
  875. value = property(get, set)
  876. def Array(typecode, sequence, lock=True):
  877. return array.array(typecode, sequence)
  878. #
  879. # Proxy types used by SyncManager
  880. #
  881. class IteratorProxy(BaseProxy):
  882. _exposed_ = ('__next__', 'send', 'throw', 'close')
  883. def __iter__(self):
  884. return self
  885. def __next__(self, *args):
  886. return self._callmethod('__next__', args)
  887. def send(self, *args):
  888. return self._callmethod('send', args)
  889. def throw(self, *args):
  890. return self._callmethod('throw', args)
  891. def close(self, *args):
  892. return self._callmethod('close', args)
  893. class AcquirerProxy(BaseProxy):
  894. _exposed_ = ('acquire', 'release')
  895. def acquire(self, blocking=True, timeout=None):
  896. args = (blocking,) if timeout is None else (blocking, timeout)
  897. return self._callmethod('acquire', args)
  898. def release(self):
  899. return self._callmethod('release')
  900. def __enter__(self):
  901. return self._callmethod('acquire')
  902. def __exit__(self, exc_type, exc_val, exc_tb):
  903. return self._callmethod('release')
  904. class ConditionProxy(AcquirerProxy):
  905. _exposed_ = ('acquire', 'release', 'wait', 'notify', 'notify_all')
  906. def wait(self, timeout=None):
  907. return self._callmethod('wait', (timeout,))
  908. def notify(self, n=1):
  909. return self._callmethod('notify', (n,))
  910. def notify_all(self):
  911. return self._callmethod('notify_all')
  912. def wait_for(self, predicate, timeout=None):
  913. result = predicate()
  914. if result:
  915. return result
  916. if timeout is not None:
  917. endtime = time.monotonic() + timeout
  918. else:
  919. endtime = None
  920. waittime = None
  921. while not result:
  922. if endtime is not None:
  923. waittime = endtime - time.monotonic()
  924. if waittime <= 0:
  925. break
  926. self.wait(waittime)
  927. result = predicate()
  928. return result
  929. class EventProxy(BaseProxy):
  930. _exposed_ = ('is_set', 'set', 'clear', 'wait')
  931. def is_set(self):
  932. return self._callmethod('is_set')
  933. def set(self):
  934. return self._callmethod('set')
  935. def clear(self):
  936. return self._callmethod('clear')
  937. def wait(self, timeout=None):
  938. return self._callmethod('wait', (timeout,))
  939. class BarrierProxy(BaseProxy):
  940. _exposed_ = ('__getattribute__', 'wait', 'abort', 'reset')
  941. def wait(self, timeout=None):
  942. return self._callmethod('wait', (timeout,))
  943. def abort(self):
  944. return self._callmethod('abort')
  945. def reset(self):
  946. return self._callmethod('reset')
  947. @property
  948. def parties(self):
  949. return self._callmethod('__getattribute__', ('parties',))
  950. @property
  951. def n_waiting(self):
  952. return self._callmethod('__getattribute__', ('n_waiting',))
  953. @property
  954. def broken(self):
  955. return self._callmethod('__getattribute__', ('broken',))
  956. class NamespaceProxy(BaseProxy):
  957. _exposed_ = ('__getattribute__', '__setattr__', '__delattr__')
  958. def __getattr__(self, key):
  959. if key[0] == '_':
  960. return object.__getattribute__(self, key)
  961. callmethod = object.__getattribute__(self, '_callmethod')
  962. return callmethod('__getattribute__', (key,))
  963. def __setattr__(self, key, value):
  964. if key[0] == '_':
  965. return object.__setattr__(self, key, value)
  966. callmethod = object.__getattribute__(self, '_callmethod')
  967. return callmethod('__setattr__', (key, value))
  968. def __delattr__(self, key):
  969. if key[0] == '_':
  970. return object.__delattr__(self, key)
  971. callmethod = object.__getattribute__(self, '_callmethod')
  972. return callmethod('__delattr__', (key,))
  973. class ValueProxy(BaseProxy):
  974. _exposed_ = ('get', 'set')
  975. def get(self):
  976. return self._callmethod('get')
  977. def set(self, value):
  978. return self._callmethod('set', (value,))
  979. value = property(get, set)
  980. BaseListProxy = MakeProxyType('BaseListProxy', (
  981. '__add__', '__contains__', '__delitem__', '__getitem__', '__len__',
  982. '__mul__', '__reversed__', '__rmul__', '__setitem__',
  983. 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove',
  984. 'reverse', 'sort', '__imul__'
  985. ))
  986. class ListProxy(BaseListProxy):
  987. def __iadd__(self, value):
  988. self._callmethod('extend', (value,))
  989. return self
  990. def __imul__(self, value):
  991. self._callmethod('__imul__', (value,))
  992. return self
  993. DictProxy = MakeProxyType('DictProxy', (
  994. '__contains__', '__delitem__', '__getitem__', '__iter__', '__len__',
  995. '__setitem__', 'clear', 'copy', 'get', 'items',
  996. 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'
  997. ))
  998. DictProxy._method_to_typeid_ = {
  999. '__iter__': 'Iterator',
  1000. }
  1001. ArrayProxy = MakeProxyType('ArrayProxy', (
  1002. '__len__', '__getitem__', '__setitem__'
  1003. ))
  1004. BasePoolProxy = MakeProxyType('PoolProxy', (
  1005. 'apply', 'apply_async', 'close', 'imap', 'imap_unordered', 'join',
  1006. 'map', 'map_async', 'starmap', 'starmap_async', 'terminate',
  1007. ))
  1008. BasePoolProxy._method_to_typeid_ = {
  1009. 'apply_async': 'AsyncResult',
  1010. 'map_async': 'AsyncResult',
  1011. 'starmap_async': 'AsyncResult',
  1012. 'imap': 'Iterator',
  1013. 'imap_unordered': 'Iterator'
  1014. }
  1015. class PoolProxy(BasePoolProxy):
  1016. def __enter__(self):
  1017. return self
  1018. def __exit__(self, exc_type, exc_val, exc_tb):
  1019. self.terminate()
  1020. #
  1021. # Definition of SyncManager
  1022. #
  1023. class SyncManager(BaseManager):
  1024. '''
  1025. Subclass of `BaseManager` which supports a number of shared object types.
  1026. The types registered are those intended for the synchronization
  1027. of threads, plus `dict`, `list` and `Namespace`.
  1028. The `multiprocessing.Manager()` function creates started instances of
  1029. this class.
  1030. '''
  1031. SyncManager.register('Queue', queue.Queue)
  1032. SyncManager.register('JoinableQueue', queue.Queue)
  1033. SyncManager.register('Event', threading.Event, EventProxy)
  1034. SyncManager.register('Lock', threading.Lock, AcquirerProxy)
  1035. SyncManager.register('RLock', threading.RLock, AcquirerProxy)
  1036. SyncManager.register('Semaphore', threading.Semaphore, AcquirerProxy)
  1037. SyncManager.register('BoundedSemaphore', threading.BoundedSemaphore,
  1038. AcquirerProxy)
  1039. SyncManager.register('Condition', threading.Condition, ConditionProxy)
  1040. SyncManager.register('Barrier', threading.Barrier, BarrierProxy)
  1041. SyncManager.register('Pool', pool.Pool, PoolProxy)
  1042. SyncManager.register('list', list, ListProxy)
  1043. SyncManager.register('dict', dict, DictProxy)
  1044. SyncManager.register('Value', Value, ValueProxy)
  1045. SyncManager.register('Array', Array, ArrayProxy)
  1046. SyncManager.register('Namespace', Namespace, NamespaceProxy)
  1047. # types returned by methods of PoolProxy
  1048. SyncManager.register('Iterator', proxytype=IteratorProxy, create_method=False)
  1049. SyncManager.register('AsyncResult', create_method=False)