Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

487 lignes
17KB

  1. import errno
  2. import heapq
  3. import math
  4. import signal
  5. import sys
  6. import traceback
  7. arm_alarm = None
  8. if hasattr(signal, 'setitimer'):
  9. def alarm_itimer(seconds):
  10. signal.setitimer(signal.ITIMER_REAL, seconds)
  11. arm_alarm = alarm_itimer
  12. else:
  13. try:
  14. import itimer
  15. arm_alarm = itimer.alarm
  16. except ImportError:
  17. def alarm_signal(seconds):
  18. signal.alarm(math.ceil(seconds))
  19. arm_alarm = alarm_signal
  20. import eventlet.hubs
  21. from eventlet.hubs import timer
  22. from eventlet.support import greenlets as greenlet, clear_sys_exc_info
  23. import monotonic
  24. import six
  25. g_prevent_multiple_readers = True
  26. READ = "read"
  27. WRITE = "write"
  28. def closed_callback(fileno):
  29. """ Used to de-fang a callback that may be triggered by a loop in BaseHub.wait
  30. """
  31. # No-op.
  32. pass
  33. class FdListener(object):
  34. def __init__(self, evtype, fileno, cb, tb, mark_as_closed):
  35. """ The following are required:
  36. cb - the standard callback, which will switch into the
  37. listening greenlet to indicate that the event waited upon
  38. is ready
  39. tb - a 'throwback'. This is typically greenlet.throw, used
  40. to raise a signal into the target greenlet indicating that
  41. an event was obsoleted by its underlying filehandle being
  42. repurposed.
  43. mark_as_closed - if any listener is obsoleted, this is called
  44. (in the context of some other client greenlet) to alert
  45. underlying filehandle-wrapping objects that they've been
  46. closed.
  47. """
  48. assert (evtype is READ or evtype is WRITE)
  49. self.evtype = evtype
  50. self.fileno = fileno
  51. self.cb = cb
  52. self.tb = tb
  53. self.mark_as_closed = mark_as_closed
  54. self.spent = False
  55. self.greenlet = greenlet.getcurrent()
  56. def __repr__(self):
  57. return "%s(%r, %r, %r, %r)" % (type(self).__name__, self.evtype, self.fileno,
  58. self.cb, self.tb)
  59. __str__ = __repr__
  60. def defang(self):
  61. self.cb = closed_callback
  62. if self.mark_as_closed is not None:
  63. self.mark_as_closed()
  64. self.spent = True
  65. noop = FdListener(READ, 0, lambda x: None, lambda x: None, None)
  66. # in debug mode, track the call site that created the listener
  67. class DebugListener(FdListener):
  68. def __init__(self, evtype, fileno, cb, tb, mark_as_closed):
  69. self.where_called = traceback.format_stack()
  70. self.greenlet = greenlet.getcurrent()
  71. super(DebugListener, self).__init__(evtype, fileno, cb, tb, mark_as_closed)
  72. def __repr__(self):
  73. return "DebugListener(%r, %r, %r, %r, %r, %r)\n%sEndDebugFdListener" % (
  74. self.evtype,
  75. self.fileno,
  76. self.cb,
  77. self.tb,
  78. self.mark_as_closed,
  79. self.greenlet,
  80. ''.join(self.where_called))
  81. __str__ = __repr__
  82. def alarm_handler(signum, frame):
  83. import inspect
  84. raise RuntimeError("Blocking detector ALARMED at" + str(inspect.getframeinfo(frame)))
  85. class BaseHub(object):
  86. """ Base hub class for easing the implementation of subclasses that are
  87. specific to a particular underlying event architecture. """
  88. SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit)
  89. READ = READ
  90. WRITE = WRITE
  91. def __init__(self, clock=None):
  92. self.listeners = {READ: {}, WRITE: {}}
  93. self.secondaries = {READ: {}, WRITE: {}}
  94. self.closed = []
  95. if clock is None:
  96. clock = monotonic.monotonic
  97. self.clock = clock
  98. self.greenlet = greenlet.greenlet(self.run)
  99. self.stopping = False
  100. self.running = False
  101. self.timers = []
  102. self.next_timers = []
  103. self.lclass = FdListener
  104. self.timers_canceled = 0
  105. self.debug_exceptions = True
  106. self.debug_blocking = False
  107. self.debug_blocking_resolution = 1
  108. def block_detect_pre(self):
  109. # shortest alarm we can possibly raise is one second
  110. tmp = signal.signal(signal.SIGALRM, alarm_handler)
  111. if tmp != alarm_handler:
  112. self._old_signal_handler = tmp
  113. arm_alarm(self.debug_blocking_resolution)
  114. def block_detect_post(self):
  115. if (hasattr(self, "_old_signal_handler") and
  116. self._old_signal_handler):
  117. signal.signal(signal.SIGALRM, self._old_signal_handler)
  118. signal.alarm(0)
  119. def add(self, evtype, fileno, cb, tb, mark_as_closed):
  120. """ Signals an intent to or write a particular file descriptor.
  121. The *evtype* argument is either the constant READ or WRITE.
  122. The *fileno* argument is the file number of the file of interest.
  123. The *cb* argument is the callback which will be called when the file
  124. is ready for reading/writing.
  125. The *tb* argument is the throwback used to signal (into the greenlet)
  126. that the file was closed.
  127. The *mark_as_closed* is used in the context of the event hub to
  128. prepare a Python object as being closed, pre-empting further
  129. close operations from accidentally shutting down the wrong OS thread.
  130. """
  131. listener = self.lclass(evtype, fileno, cb, tb, mark_as_closed)
  132. bucket = self.listeners[evtype]
  133. if fileno in bucket:
  134. if g_prevent_multiple_readers:
  135. raise RuntimeError(
  136. "Second simultaneous %s on fileno %s "
  137. "detected. Unless you really know what you're doing, "
  138. "make sure that only one greenthread can %s any "
  139. "particular socket. Consider using a pools.Pool. "
  140. "If you do know what you're doing and want to disable "
  141. "this error, call "
  142. "eventlet.debug.hub_prevent_multiple_readers(False) - MY THREAD=%s; "
  143. "THAT THREAD=%s" % (
  144. evtype, fileno, evtype, cb, bucket[fileno]))
  145. # store off the second listener in another structure
  146. self.secondaries[evtype].setdefault(fileno, []).append(listener)
  147. else:
  148. bucket[fileno] = listener
  149. return listener
  150. def _obsolete(self, fileno):
  151. """ We've received an indication that 'fileno' has been obsoleted.
  152. Any current listeners must be defanged, and notifications to
  153. their greenlets queued up to send.
  154. """
  155. found = False
  156. for evtype, bucket in six.iteritems(self.secondaries):
  157. if fileno in bucket:
  158. for listener in bucket[fileno]:
  159. found = True
  160. self.closed.append(listener)
  161. listener.defang()
  162. del bucket[fileno]
  163. # For the primary listeners, we actually need to call remove,
  164. # which may modify the underlying OS polling objects.
  165. for evtype, bucket in six.iteritems(self.listeners):
  166. if fileno in bucket:
  167. listener = bucket[fileno]
  168. found = True
  169. self.closed.append(listener)
  170. self.remove(listener)
  171. listener.defang()
  172. return found
  173. def notify_close(self, fileno):
  174. """ We might want to do something when a fileno is closed.
  175. However, currently it suffices to obsolete listeners only
  176. when we detect an old fileno being recycled, on open.
  177. """
  178. pass
  179. def remove(self, listener):
  180. if listener.spent:
  181. # trampoline may trigger this in its finally section.
  182. return
  183. fileno = listener.fileno
  184. evtype = listener.evtype
  185. self.listeners[evtype].pop(fileno, None)
  186. # migrate a secondary listener to be the primary listener
  187. if fileno in self.secondaries[evtype]:
  188. sec = self.secondaries[evtype].get(fileno, None)
  189. if not sec:
  190. return
  191. self.listeners[evtype][fileno] = sec.pop(0)
  192. if not sec:
  193. del self.secondaries[evtype][fileno]
  194. def mark_as_reopened(self, fileno):
  195. """ If a file descriptor is returned by the OS as the result of some
  196. open call (or equivalent), that signals that it might be being
  197. recycled.
  198. Catch the case where the fd was previously in use.
  199. """
  200. self._obsolete(fileno)
  201. def remove_descriptor(self, fileno):
  202. """ Completely remove all listeners for this fileno. For internal use
  203. only."""
  204. listeners = []
  205. listeners.append(self.listeners[READ].pop(fileno, noop))
  206. listeners.append(self.listeners[WRITE].pop(fileno, noop))
  207. listeners.extend(self.secondaries[READ].pop(fileno, ()))
  208. listeners.extend(self.secondaries[WRITE].pop(fileno, ()))
  209. for listener in listeners:
  210. try:
  211. listener.cb(fileno)
  212. except Exception:
  213. self.squelch_generic_exception(sys.exc_info())
  214. def close_one(self):
  215. """ Triggered from the main run loop. If a listener's underlying FD was
  216. closed somehow, throw an exception back to the trampoline, which should
  217. be able to manage it appropriately.
  218. """
  219. listener = self.closed.pop()
  220. if not listener.greenlet.dead:
  221. # There's no point signalling a greenlet that's already dead.
  222. listener.tb(eventlet.hubs.IOClosed(errno.ENOTCONN, "Operation on closed file"))
  223. def ensure_greenlet(self):
  224. if self.greenlet.dead:
  225. # create new greenlet sharing same parent as original
  226. new = greenlet.greenlet(self.run, self.greenlet.parent)
  227. # need to assign as parent of old greenlet
  228. # for those greenlets that are currently
  229. # children of the dead hub and may subsequently
  230. # exit without further switching to hub.
  231. self.greenlet.parent = new
  232. self.greenlet = new
  233. def switch(self):
  234. cur = greenlet.getcurrent()
  235. assert cur is not self.greenlet, 'Cannot switch to MAINLOOP from MAINLOOP'
  236. switch_out = getattr(cur, 'switch_out', None)
  237. if switch_out is not None:
  238. try:
  239. switch_out()
  240. except:
  241. self.squelch_generic_exception(sys.exc_info())
  242. self.ensure_greenlet()
  243. try:
  244. if self.greenlet.parent is not cur:
  245. cur.parent = self.greenlet
  246. except ValueError:
  247. pass # gets raised if there is a greenlet parent cycle
  248. clear_sys_exc_info()
  249. return self.greenlet.switch()
  250. def squelch_exception(self, fileno, exc_info):
  251. traceback.print_exception(*exc_info)
  252. sys.stderr.write("Removing descriptor: %r\n" % (fileno,))
  253. sys.stderr.flush()
  254. try:
  255. self.remove_descriptor(fileno)
  256. except Exception as e:
  257. sys.stderr.write("Exception while removing descriptor! %r\n" % (e,))
  258. sys.stderr.flush()
  259. def wait(self, seconds=None):
  260. raise NotImplementedError("Implement this in a subclass")
  261. def default_sleep(self):
  262. return 60.0
  263. def sleep_until(self):
  264. t = self.timers
  265. if not t:
  266. return None
  267. return t[0][0]
  268. def run(self, *a, **kw):
  269. """Run the runloop until abort is called.
  270. """
  271. # accept and discard variable arguments because they will be
  272. # supplied if other greenlets have run and exited before the
  273. # hub's greenlet gets a chance to run
  274. if self.running:
  275. raise RuntimeError("Already running!")
  276. try:
  277. self.running = True
  278. self.stopping = False
  279. while not self.stopping:
  280. while self.closed:
  281. # We ditch all of these first.
  282. self.close_one()
  283. self.prepare_timers()
  284. if self.debug_blocking:
  285. self.block_detect_pre()
  286. self.fire_timers(self.clock())
  287. if self.debug_blocking:
  288. self.block_detect_post()
  289. self.prepare_timers()
  290. wakeup_when = self.sleep_until()
  291. if wakeup_when is None:
  292. sleep_time = self.default_sleep()
  293. else:
  294. sleep_time = wakeup_when - self.clock()
  295. if sleep_time > 0:
  296. self.wait(sleep_time)
  297. else:
  298. self.wait(0)
  299. else:
  300. self.timers_canceled = 0
  301. del self.timers[:]
  302. del self.next_timers[:]
  303. finally:
  304. self.running = False
  305. self.stopping = False
  306. def abort(self, wait=False):
  307. """Stop the runloop. If run is executing, it will exit after
  308. completing the next runloop iteration.
  309. Set *wait* to True to cause abort to switch to the hub immediately and
  310. wait until it's finished processing. Waiting for the hub will only
  311. work from the main greenthread; all other greenthreads will become
  312. unreachable.
  313. """
  314. if self.running:
  315. self.stopping = True
  316. if wait:
  317. assert self.greenlet is not greenlet.getcurrent(
  318. ), "Can't abort with wait from inside the hub's greenlet."
  319. # schedule an immediate timer just so the hub doesn't sleep
  320. self.schedule_call_global(0, lambda: None)
  321. # switch to it; when done the hub will switch back to its parent,
  322. # the main greenlet
  323. self.switch()
  324. def squelch_generic_exception(self, exc_info):
  325. if self.debug_exceptions:
  326. traceback.print_exception(*exc_info)
  327. sys.stderr.flush()
  328. clear_sys_exc_info()
  329. def squelch_timer_exception(self, timer, exc_info):
  330. if self.debug_exceptions:
  331. traceback.print_exception(*exc_info)
  332. sys.stderr.flush()
  333. clear_sys_exc_info()
  334. def add_timer(self, timer):
  335. scheduled_time = self.clock() + timer.seconds
  336. self.next_timers.append((scheduled_time, timer))
  337. return scheduled_time
  338. def timer_canceled(self, timer):
  339. self.timers_canceled += 1
  340. len_timers = len(self.timers) + len(self.next_timers)
  341. if len_timers > 1000 and len_timers / 2 <= self.timers_canceled:
  342. self.timers_canceled = 0
  343. self.timers = [t for t in self.timers if not t[1].called]
  344. self.next_timers = [t for t in self.next_timers if not t[1].called]
  345. heapq.heapify(self.timers)
  346. def prepare_timers(self):
  347. heappush = heapq.heappush
  348. t = self.timers
  349. for item in self.next_timers:
  350. if item[1].called:
  351. self.timers_canceled -= 1
  352. else:
  353. heappush(t, item)
  354. del self.next_timers[:]
  355. def schedule_call_local(self, seconds, cb, *args, **kw):
  356. """Schedule a callable to be called after 'seconds' seconds have
  357. elapsed. Cancel the timer if greenlet has exited.
  358. seconds: The number of seconds to wait.
  359. cb: The callable to call after the given time.
  360. *args: Arguments to pass to the callable when called.
  361. **kw: Keyword arguments to pass to the callable when called.
  362. """
  363. t = timer.LocalTimer(seconds, cb, *args, **kw)
  364. self.add_timer(t)
  365. return t
  366. def schedule_call_global(self, seconds, cb, *args, **kw):
  367. """Schedule a callable to be called after 'seconds' seconds have
  368. elapsed. The timer will NOT be canceled if the current greenlet has
  369. exited before the timer fires.
  370. seconds: The number of seconds to wait.
  371. cb: The callable to call after the given time.
  372. *args: Arguments to pass to the callable when called.
  373. **kw: Keyword arguments to pass to the callable when called.
  374. """
  375. t = timer.Timer(seconds, cb, *args, **kw)
  376. self.add_timer(t)
  377. return t
  378. def fire_timers(self, when):
  379. t = self.timers
  380. heappop = heapq.heappop
  381. while t:
  382. next = t[0]
  383. exp = next[0]
  384. timer = next[1]
  385. if when < exp:
  386. break
  387. heappop(t)
  388. try:
  389. if timer.called:
  390. self.timers_canceled -= 1
  391. else:
  392. timer()
  393. except self.SYSTEM_EXCEPTIONS:
  394. raise
  395. except:
  396. self.squelch_timer_exception(timer, sys.exc_info())
  397. clear_sys_exc_info()
  398. # for debugging:
  399. def get_readers(self):
  400. return self.listeners[READ].values()
  401. def get_writers(self):
  402. return self.listeners[WRITE].values()
  403. def get_timers_count(hub):
  404. return len(hub.timers) + len(hub.next_timers)
  405. def set_debug_listeners(self, value):
  406. if value:
  407. self.lclass = DebugListener
  408. else:
  409. self.lclass = FdListener
  410. def set_timer_exceptions(self, value):
  411. self.debug_exceptions = value