PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/pypy/module/signal/interp_signal.py

https://bitbucket.org/pypy/pypy/
Python | 332 lines | 240 code | 41 blank | 51 comment | 29 complexity | 86794fd8fe19017feb06755e42459e88 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. from __future__ import with_statement
  2. import signal as cpy_signal
  3. import sys
  4. import os
  5. import errno
  6. from pypy.interpreter.error import (
  7. OperationError, exception_from_saved_errno, oefmt)
  8. from pypy.interpreter.executioncontext import (AsyncAction, AbstractActionFlag,
  9. PeriodicAsyncAction)
  10. from pypy.interpreter.gateway import unwrap_spec
  11. from rpython.rlib import jit, rposix, rgc
  12. from rpython.rlib.objectmodel import we_are_translated
  13. from rpython.rlib.rarithmetic import intmask
  14. from rpython.rlib.rsignal import *
  15. from rpython.rtyper.lltypesystem import lltype, rffi
  16. WIN32 = sys.platform == 'win32'
  17. class SignalActionFlag(AbstractActionFlag):
  18. # This class uses the C-level pypysig_counter variable as the tick
  19. # counter. The C-level signal handler will reset it to -1 whenever
  20. # a signal is received. This causes CheckSignalAction.perform() to
  21. # be called.
  22. def get_ticker(self):
  23. p = pypysig_getaddr_occurred()
  24. return p.c_value
  25. def reset_ticker(self, value):
  26. p = pypysig_getaddr_occurred()
  27. p.c_value = value
  28. def rearm_ticker(self):
  29. p = pypysig_getaddr_occurred()
  30. p.c_value = -1
  31. def decrement_ticker(self, by):
  32. p = pypysig_getaddr_occurred()
  33. value = p.c_value
  34. if self.has_bytecode_counter: # this 'if' is constant-folded
  35. if jit.isconstant(by) and by == 0:
  36. pass # normally constant-folded too
  37. else:
  38. value -= by
  39. p.c_value = value
  40. return value
  41. class CheckSignalAction(PeriodicAsyncAction):
  42. """An action that is automatically invoked when a signal is received."""
  43. # Note that this is a PeriodicAsyncAction: it means more precisely
  44. # that it is called whenever the C-level ticker becomes < 0.
  45. # Without threads, it is only ever set to -1 when we receive a
  46. # signal. With threads, it also decrements steadily (but slowly).
  47. def __init__(self, space):
  48. "NOT_RPYTHON"
  49. AsyncAction.__init__(self, space)
  50. self.pending_signal = -1
  51. self.fire_in_another_thread = False
  52. #
  53. @rgc.no_collect
  54. def _after_thread_switch():
  55. if self.fire_in_another_thread:
  56. if self.space.threadlocals.signals_enabled():
  57. self.fire_in_another_thread = False
  58. self.space.actionflag.rearm_ticker()
  59. # this occurs when we just switched to the main thread
  60. # and there is a signal pending: we force the ticker to
  61. # -1, which should ensure perform() is called quickly.
  62. self._after_thread_switch = _after_thread_switch
  63. # ^^^ so that 'self._after_thread_switch' can be annotated as a
  64. # constant
  65. def startup(self, space):
  66. # this is translated
  67. if space.config.objspace.usemodules.thread:
  68. from rpython.rlib import rgil
  69. rgil.invoke_after_thread_switch(self._after_thread_switch)
  70. def perform(self, executioncontext, frame):
  71. self._poll_for_signals()
  72. @jit.dont_look_inside
  73. def _poll_for_signals(self):
  74. # Poll for the next signal, if any
  75. n = self.pending_signal
  76. if n < 0:
  77. n = pypysig_poll()
  78. while n >= 0:
  79. if self.space.threadlocals.signals_enabled():
  80. # If we are in the main thread, report the signal now,
  81. # and poll more
  82. self.pending_signal = -1
  83. report_signal(self.space, n)
  84. n = self.pending_signal
  85. if n < 0:
  86. n = pypysig_poll()
  87. else:
  88. # Otherwise, arrange for perform() to be called again
  89. # after we switch to the main thread.
  90. self.pending_signal = n
  91. self.fire_in_another_thread = True
  92. break
  93. def set_interrupt(self):
  94. "Simulates the effect of a SIGINT signal arriving"
  95. if not we_are_translated():
  96. self.pending_signal = cpy_signal.SIGINT
  97. # ^^^ may override another signal, but it's just for testing
  98. self.fire_in_another_thread = True
  99. else:
  100. pypysig_pushback(cpy_signal.SIGINT)
  101. # ____________________________________________________________
  102. class Handlers:
  103. def __init__(self, space):
  104. self.handlers_w = {}
  105. for signum in range(1, NSIG):
  106. if WIN32 and signum not in signal_values:
  107. self.handlers_w[signum] = space.w_None
  108. else:
  109. self.handlers_w[signum] = space.wrap(SIG_DFL)
  110. def _get_handlers(space):
  111. return space.fromcache(Handlers).handlers_w
  112. def report_signal(space, n):
  113. handlers_w = _get_handlers(space)
  114. try:
  115. w_handler = handlers_w[n]
  116. except KeyError:
  117. return # no handler, ignore signal
  118. if not space.is_true(space.callable(w_handler)):
  119. return # w_handler is SIG_IGN or SIG_DFL?
  120. # re-install signal handler, for OSes that clear it
  121. pypysig_reinstall(n)
  122. # invoke the app-level handler
  123. ec = space.getexecutioncontext()
  124. w_frame = space.wrap(ec.gettopframe_nohidden())
  125. space.call_function(w_handler, space.wrap(n), w_frame)
  126. @unwrap_spec(signum=int)
  127. def getsignal(space, signum):
  128. """
  129. getsignal(sig) -> action
  130. Return the current action for the given signal. The return value can be:
  131. SIG_IGN -- if the signal is being ignored
  132. SIG_DFL -- if the default action for the signal is in effect
  133. None -- if an unknown handler is in effect
  134. anything else -- the callable Python object used as a handler
  135. """
  136. check_signum_in_range(space, signum)
  137. handlers_w = _get_handlers(space)
  138. return handlers_w[signum]
  139. def default_int_handler(space, w_signum, w_frame):
  140. """
  141. default_int_handler(...)
  142. The default handler for SIGINT installed by Python.
  143. It raises KeyboardInterrupt.
  144. """
  145. raise OperationError(space.w_KeyboardInterrupt, space.w_None)
  146. @jit.dont_look_inside
  147. @unwrap_spec(timeout=int)
  148. def alarm(space, timeout):
  149. return space.wrap(c_alarm(timeout))
  150. @jit.dont_look_inside
  151. def pause(space):
  152. c_pause()
  153. return space.w_None
  154. def check_signum_in_range(space, signum):
  155. if 1 <= signum < NSIG:
  156. return
  157. raise oefmt(space.w_ValueError, "signal number out of range")
  158. @jit.dont_look_inside
  159. @unwrap_spec(signum=int)
  160. def signal(space, signum, w_handler):
  161. """
  162. signal(sig, action) -> action
  163. Set the action for the given signal. The action can be SIG_DFL,
  164. SIG_IGN, or a callable Python object. The previous action is
  165. returned. See getsignal() for possible return values.
  166. *** IMPORTANT NOTICE ***
  167. A signal handler function is called with two arguments:
  168. the first is the signal number, the second is the interrupted stack frame.
  169. """
  170. if WIN32 and signum not in signal_values:
  171. raise oefmt(space.w_ValueError, "invalid signal value")
  172. if not space.threadlocals.signals_enabled():
  173. raise oefmt(space.w_ValueError,
  174. "signal only works in main thread or with "
  175. "__pypy__.thread.enable_signals()")
  176. check_signum_in_range(space, signum)
  177. if space.eq_w(w_handler, space.wrap(SIG_DFL)):
  178. pypysig_default(signum)
  179. elif space.eq_w(w_handler, space.wrap(SIG_IGN)):
  180. pypysig_ignore(signum)
  181. else:
  182. if not space.is_true(space.callable(w_handler)):
  183. raise oefmt(space.w_TypeError,
  184. "'handler' must be a callable or SIG_DFL or SIG_IGN")
  185. pypysig_setflag(signum)
  186. handlers_w = _get_handlers(space)
  187. old_handler = handlers_w[signum]
  188. handlers_w[signum] = w_handler
  189. return old_handler
  190. @jit.dont_look_inside
  191. @unwrap_spec(fd=int)
  192. def set_wakeup_fd(space, fd):
  193. """Sets the fd to be written to (with '\0') when a signal
  194. comes in. Returns the old fd. A library can use this to
  195. wakeup select or poll. The previous fd is returned.
  196. The fd must be non-blocking.
  197. """
  198. if not space.threadlocals.signals_enabled():
  199. raise oefmt(space.w_ValueError,
  200. "set_wakeup_fd only works in main thread or with "
  201. "__pypy__.thread.enable_signals()")
  202. if fd != -1:
  203. try:
  204. os.fstat(fd)
  205. except OSError as e:
  206. if e.errno == errno.EBADF:
  207. raise oefmt(space.w_ValueError, "invalid fd")
  208. old_fd = pypysig_set_wakeup_fd(fd)
  209. return space.wrap(intmask(old_fd))
  210. @jit.dont_look_inside
  211. @unwrap_spec(signum=int, flag=int)
  212. def siginterrupt(space, signum, flag):
  213. check_signum_in_range(space, signum)
  214. if rffi.cast(lltype.Signed, c_siginterrupt(signum, flag)) < 0:
  215. errno = rposix.get_saved_errno()
  216. raise OperationError(space.w_RuntimeError, space.wrap(errno))
  217. #__________________________________________________________
  218. def timeval_from_double(d, timeval):
  219. rffi.setintfield(timeval, 'c_tv_sec', int(d))
  220. rffi.setintfield(timeval, 'c_tv_usec', int((d - int(d)) * 1000000))
  221. def double_from_timeval(tv):
  222. return rffi.getintfield(tv, 'c_tv_sec') + (
  223. rffi.getintfield(tv, 'c_tv_usec') / 1000000.0)
  224. def itimer_retval(space, val):
  225. w_value = space.wrap(double_from_timeval(val.c_it_value))
  226. w_interval = space.wrap(double_from_timeval(val.c_it_interval))
  227. return space.newtuple([w_value, w_interval])
  228. class Cache:
  229. def __init__(self, space):
  230. self.w_itimererror = space.new_exception_class("signal.ItimerError",
  231. space.w_IOError)
  232. def get_itimer_error(space):
  233. return space.fromcache(Cache).w_itimererror
  234. @jit.dont_look_inside
  235. @unwrap_spec(which=int, first=float, interval=float)
  236. def setitimer(space, which, first, interval=0):
  237. """setitimer(which, seconds[, interval])
  238. Sets given itimer (one of ITIMER_REAL, ITIMER_VIRTUAL
  239. or ITIMER_PROF) to fire after value seconds and after
  240. that every interval seconds.
  241. The itimer can be cleared by setting seconds to zero.
  242. Returns old values as a tuple: (delay, interval).
  243. """
  244. with lltype.scoped_alloc(itimervalP.TO, 1) as new:
  245. timeval_from_double(first, new[0].c_it_value)
  246. timeval_from_double(interval, new[0].c_it_interval)
  247. with lltype.scoped_alloc(itimervalP.TO, 1) as old:
  248. ret = c_setitimer(which, new, old)
  249. if ret != 0:
  250. raise exception_from_saved_errno(space, get_itimer_error(space))
  251. return itimer_retval(space, old[0])
  252. @jit.dont_look_inside
  253. @unwrap_spec(which=int)
  254. def getitimer(space, which):
  255. """getitimer(which)
  256. Returns current value of given itimer.
  257. """
  258. with lltype.scoped_alloc(itimervalP.TO, 1) as old:
  259. c_getitimer(which, old)
  260. return itimer_retval(space, old[0])