Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

forking.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. #
  2. # Module for starting a process object using os.fork() or CreateProcess()
  3. #
  4. # multiprocessing/forking.py
  5. #
  6. # Copyright (c) 2006-2008, R Oudkerk
  7. # Licensed to PSF under a Contributor Agreement.
  8. #
  9. from __future__ import absolute_import
  10. import os
  11. import sys
  12. import signal
  13. import warnings
  14. from pickle import load, HIGHEST_PROTOCOL
  15. from billiard import util
  16. from billiard import process
  17. from billiard.five import int_types
  18. from .reduction import dump
  19. from .compat import _winapi as win32
  20. __all__ = ['Popen', 'assert_spawning', 'exit',
  21. 'duplicate', 'close']
  22. try:
  23. WindowsError = WindowsError # noqa
  24. except NameError:
  25. class WindowsError(Exception): # noqa
  26. pass
  27. W_OLD_DJANGO_LAYOUT = """\
  28. Will add directory %r to path! This is necessary to accommodate \
  29. pre-Django 1.4 layouts using setup_environ.
  30. You can skip this warning by adding a DJANGO_SETTINGS_MODULE=settings \
  31. environment variable.
  32. """
  33. #
  34. # Choose whether to do a fork or spawn (fork+exec) on Unix.
  35. # This affects how some shared resources should be created.
  36. #
  37. _forking_is_enabled = sys.platform != 'win32'
  38. #
  39. # Check that the current thread is spawning a child process
  40. #
  41. def assert_spawning(self):
  42. if not Popen.thread_is_spawning():
  43. raise RuntimeError(
  44. '%s objects should only be shared between processes'
  45. ' through inheritance' % type(self).__name__
  46. )
  47. #
  48. # Unix
  49. #
  50. if sys.platform != 'win32':
  51. try:
  52. import thread
  53. except ImportError:
  54. import _thread as thread # noqa
  55. import select
  56. WINEXE = False
  57. WINSERVICE = False
  58. exit = os._exit
  59. duplicate = os.dup
  60. close = os.close
  61. _select = util._eintr_retry(select.select)
  62. #
  63. # We define a Popen class similar to the one from subprocess, but
  64. # whose constructor takes a process object as its argument.
  65. #
  66. class Popen(object):
  67. _tls = thread._local()
  68. def __init__(self, process_obj):
  69. # register reducers
  70. from billiard import connection # noqa
  71. _Django_old_layout_hack__save()
  72. sys.stdout.flush()
  73. sys.stderr.flush()
  74. self.returncode = None
  75. r, w = os.pipe()
  76. self.sentinel = r
  77. if _forking_is_enabled:
  78. self.pid = os.fork()
  79. if self.pid == 0:
  80. os.close(r)
  81. if 'random' in sys.modules:
  82. import random
  83. random.seed()
  84. code = process_obj._bootstrap()
  85. os._exit(code)
  86. else:
  87. from_parent_fd, to_child_fd = os.pipe()
  88. cmd = get_command_line() + [str(from_parent_fd)]
  89. self.pid = os.fork()
  90. if self.pid == 0:
  91. os.close(r)
  92. os.close(to_child_fd)
  93. os.execv(sys.executable, cmd)
  94. # send information to child
  95. prep_data = get_preparation_data(process_obj._name)
  96. os.close(from_parent_fd)
  97. to_child = os.fdopen(to_child_fd, 'wb')
  98. Popen._tls.process_handle = self.pid
  99. try:
  100. dump(prep_data, to_child, HIGHEST_PROTOCOL)
  101. dump(process_obj, to_child, HIGHEST_PROTOCOL)
  102. finally:
  103. del(Popen._tls.process_handle)
  104. to_child.close()
  105. # `w` will be closed when the child exits, at which point `r`
  106. # will become ready for reading (using e.g. select()).
  107. os.close(w)
  108. util.Finalize(self, os.close, (self.sentinel,))
  109. def poll(self, flag=os.WNOHANG):
  110. if self.returncode is None:
  111. try:
  112. pid, sts = os.waitpid(self.pid, flag)
  113. except os.error:
  114. # Child process not yet created. See #1731717
  115. # e.errno == errno.ECHILD == 10
  116. return None
  117. if pid == self.pid:
  118. if os.WIFSIGNALED(sts):
  119. self.returncode = -os.WTERMSIG(sts)
  120. else:
  121. assert os.WIFEXITED(sts)
  122. self.returncode = os.WEXITSTATUS(sts)
  123. return self.returncode
  124. def wait(self, timeout=None):
  125. if self.returncode is None:
  126. if timeout is not None:
  127. r = _select([self.sentinel], [], [], timeout)[0]
  128. if not r:
  129. return None
  130. # This shouldn't block if select() returned successfully.
  131. return self.poll(os.WNOHANG if timeout == 0.0 else 0)
  132. return self.returncode
  133. def terminate(self):
  134. if self.returncode is None:
  135. try:
  136. os.kill(self.pid, signal.SIGTERM)
  137. except OSError:
  138. if self.wait(timeout=0.1) is None:
  139. raise
  140. @staticmethod
  141. def thread_is_spawning():
  142. if _forking_is_enabled:
  143. return False
  144. else:
  145. return getattr(Popen._tls, 'process_handle', None) is not None
  146. @staticmethod
  147. def duplicate_for_child(handle):
  148. return handle
  149. #
  150. # Windows
  151. #
  152. else:
  153. try:
  154. import thread
  155. except ImportError:
  156. import _thread as thread # noqa
  157. import msvcrt
  158. try:
  159. import _subprocess
  160. except ImportError:
  161. import _winapi as _subprocess # noqa
  162. #
  163. #
  164. #
  165. TERMINATE = 0x10000
  166. WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
  167. WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")
  168. exit = win32.ExitProcess
  169. close = win32.CloseHandle
  170. #
  171. #
  172. #
  173. def duplicate(handle, target_process=None, inheritable=False):
  174. if target_process is None:
  175. target_process = _subprocess.GetCurrentProcess()
  176. h = _subprocess.DuplicateHandle(
  177. _subprocess.GetCurrentProcess(), handle, target_process,
  178. 0, inheritable, _subprocess.DUPLICATE_SAME_ACCESS
  179. )
  180. if sys.version_info[0] < 3 or (
  181. sys.version_info[0] == 3 and sys.version_info[1] < 3):
  182. h = h.Detach()
  183. return h
  184. #
  185. # We define a Popen class similar to the one from subprocess, but
  186. # whose constructor takes a process object as its argument.
  187. #
  188. class Popen(object):
  189. '''
  190. Start a subprocess to run the code of a process object
  191. '''
  192. _tls = thread._local()
  193. def __init__(self, process_obj):
  194. _Django_old_layout_hack__save()
  195. # create pipe for communication with child
  196. rfd, wfd = os.pipe()
  197. # get handle for read end of the pipe and make it inheritable
  198. rhandle = duplicate(msvcrt.get_osfhandle(rfd), inheritable=True)
  199. os.close(rfd)
  200. # start process
  201. cmd = get_command_line() + [rhandle]
  202. cmd = ' '.join('"%s"' % x for x in cmd)
  203. hp, ht, pid, tid = _subprocess.CreateProcess(
  204. _python_exe, cmd, None, None, 1, 0, None, None, None
  205. )
  206. close(ht) if isinstance(ht, int_types) else ht.Close()
  207. (close(rhandle) if isinstance(rhandle, int_types)
  208. else rhandle.Close())
  209. # set attributes of self
  210. self.pid = pid
  211. self.returncode = None
  212. self._handle = hp
  213. self.sentinel = int(hp)
  214. # send information to child
  215. prep_data = get_preparation_data(process_obj._name)
  216. to_child = os.fdopen(wfd, 'wb')
  217. Popen._tls.process_handle = int(hp)
  218. try:
  219. dump(prep_data, to_child, HIGHEST_PROTOCOL)
  220. dump(process_obj, to_child, HIGHEST_PROTOCOL)
  221. finally:
  222. del Popen._tls.process_handle
  223. to_child.close()
  224. @staticmethod
  225. def thread_is_spawning():
  226. return getattr(Popen._tls, 'process_handle', None) is not None
  227. @staticmethod
  228. def duplicate_for_child(handle):
  229. return duplicate(handle, Popen._tls.process_handle)
  230. def wait(self, timeout=None):
  231. if self.returncode is None:
  232. if timeout is None:
  233. msecs = _subprocess.INFINITE
  234. else:
  235. msecs = max(0, int(timeout * 1000 + 0.5))
  236. res = _subprocess.WaitForSingleObject(int(self._handle), msecs)
  237. if res == _subprocess.WAIT_OBJECT_0:
  238. code = _subprocess.GetExitCodeProcess(self._handle)
  239. if code == TERMINATE:
  240. code = -signal.SIGTERM
  241. self.returncode = code
  242. return self.returncode
  243. def poll(self):
  244. return self.wait(timeout=0)
  245. def terminate(self):
  246. if self.returncode is None:
  247. try:
  248. _subprocess.TerminateProcess(int(self._handle), TERMINATE)
  249. except WindowsError:
  250. if self.wait(timeout=0.1) is None:
  251. raise
  252. #
  253. #
  254. #
  255. if WINSERVICE:
  256. _python_exe = os.path.join(sys.exec_prefix, 'python.exe')
  257. else:
  258. _python_exe = sys.executable
  259. def set_executable(exe):
  260. global _python_exe
  261. _python_exe = exe
  262. def is_forking(argv):
  263. '''
  264. Return whether commandline indicates we are forking
  265. '''
  266. if len(argv) >= 2 and argv[1] == '--billiard-fork':
  267. assert len(argv) == 3
  268. os.environ["FORKED_BY_MULTIPROCESSING"] = "1"
  269. return True
  270. else:
  271. return False
  272. def freeze_support():
  273. '''
  274. Run code for process object if this in not the main process
  275. '''
  276. if is_forking(sys.argv):
  277. main()
  278. sys.exit()
  279. def get_command_line():
  280. '''
  281. Returns prefix of command line used for spawning a child process
  282. '''
  283. if process.current_process()._identity == () and is_forking(sys.argv):
  284. raise RuntimeError('''
  285. Attempt to start a new process before the current process
  286. has finished its bootstrapping phase.
  287. This probably means that have forgotten to use the proper
  288. idiom in the main module:
  289. if __name__ == '__main__':
  290. freeze_support()
  291. ...
  292. The "freeze_support()" line can be omitted if the program
  293. is not going to be frozen to produce a Windows executable.''')
  294. if getattr(sys, 'frozen', False):
  295. return [sys.executable, '--billiard-fork']
  296. else:
  297. prog = 'from billiard.forking import main; main()'
  298. return [_python_exe, '-c', prog, '--billiard-fork']
  299. def _Django_old_layout_hack__save():
  300. if 'DJANGO_PROJECT_DIR' not in os.environ:
  301. try:
  302. settings_name = os.environ['DJANGO_SETTINGS_MODULE']
  303. except KeyError:
  304. return # not using Django.
  305. conf_settings = sys.modules.get('django.conf.settings')
  306. configured = conf_settings and conf_settings.configured
  307. try:
  308. project_name, _ = settings_name.split('.', 1)
  309. except ValueError:
  310. return # not modified by setup_environ
  311. project = __import__(project_name)
  312. try:
  313. project_dir = os.path.normpath(_module_parent_dir(project))
  314. except AttributeError:
  315. return # dynamically generated module (no __file__)
  316. if configured:
  317. warnings.warn(UserWarning(
  318. W_OLD_DJANGO_LAYOUT % os.path.realpath(project_dir)
  319. ))
  320. os.environ['DJANGO_PROJECT_DIR'] = project_dir
  321. def _Django_old_layout_hack__load():
  322. try:
  323. sys.path.append(os.environ['DJANGO_PROJECT_DIR'])
  324. except KeyError:
  325. pass
  326. def _module_parent_dir(mod):
  327. dir, filename = os.path.split(_module_dir(mod))
  328. if dir == os.curdir or not dir:
  329. dir = os.getcwd()
  330. return dir
  331. def _module_dir(mod):
  332. if '__init__.py' in mod.__file__:
  333. return os.path.dirname(mod.__file__)
  334. return mod.__file__
  335. def main():
  336. '''
  337. Run code specifed by data received over pipe
  338. '''
  339. global _forking_is_enabled
  340. _Django_old_layout_hack__load()
  341. assert is_forking(sys.argv)
  342. _forking_is_enabled = False
  343. handle = int(sys.argv[-1])
  344. if sys.platform == 'win32':
  345. fd = msvcrt.open_osfhandle(handle, os.O_RDONLY)
  346. else:
  347. fd = handle
  348. from_parent = os.fdopen(fd, 'rb')
  349. process.current_process()._inheriting = True
  350. preparation_data = load(from_parent)
  351. prepare(preparation_data)
  352. # Huge hack to make logging before Process.run work.
  353. try:
  354. os.environ["MP_MAIN_FILE"] = sys.modules["__main__"].__file__
  355. except KeyError:
  356. pass
  357. except AttributeError:
  358. pass
  359. loglevel = os.environ.get("_MP_FORK_LOGLEVEL_")
  360. logfile = os.environ.get("_MP_FORK_LOGFILE_") or None
  361. format = os.environ.get("_MP_FORK_LOGFORMAT_")
  362. if loglevel:
  363. from billiard import util
  364. import logging
  365. logger = util.get_logger()
  366. logger.setLevel(int(loglevel))
  367. if not logger.handlers:
  368. logger._rudimentary_setup = True
  369. logfile = logfile or sys.__stderr__
  370. if hasattr(logfile, "write"):
  371. handler = logging.StreamHandler(logfile)
  372. else:
  373. handler = logging.FileHandler(logfile)
  374. formatter = logging.Formatter(
  375. format or util.DEFAULT_LOGGING_FORMAT,
  376. )
  377. handler.setFormatter(formatter)
  378. logger.addHandler(handler)
  379. self = load(from_parent)
  380. process.current_process()._inheriting = False
  381. from_parent.close()
  382. exitcode = self._bootstrap()
  383. exit(exitcode)
  384. def get_preparation_data(name):
  385. '''
  386. Return info about parent needed by child to unpickle process object
  387. '''
  388. from billiard.util import _logger, _log_to_stderr
  389. d = dict(
  390. name=name,
  391. sys_path=sys.path,
  392. sys_argv=sys.argv,
  393. log_to_stderr=_log_to_stderr,
  394. orig_dir=process.ORIGINAL_DIR,
  395. authkey=process.current_process().authkey,
  396. )
  397. if _logger is not None:
  398. d['log_level'] = _logger.getEffectiveLevel()
  399. if not WINEXE and not WINSERVICE:
  400. main_path = getattr(sys.modules['__main__'], '__file__', None)
  401. if not main_path and sys.argv[0] not in ('', '-c'):
  402. main_path = sys.argv[0]
  403. if main_path is not None:
  404. if (not os.path.isabs(main_path) and
  405. process.ORIGINAL_DIR is not None):
  406. main_path = os.path.join(process.ORIGINAL_DIR, main_path)
  407. d['main_path'] = os.path.normpath(main_path)
  408. return d
  409. #
  410. # Prepare current process
  411. #
  412. old_main_modules = []
  413. def prepare(data):
  414. '''
  415. Try to get current process ready to unpickle process object
  416. '''
  417. old_main_modules.append(sys.modules['__main__'])
  418. if 'name' in data:
  419. process.current_process().name = data['name']
  420. if 'authkey' in data:
  421. process.current_process()._authkey = data['authkey']
  422. if 'log_to_stderr' in data and data['log_to_stderr']:
  423. util.log_to_stderr()
  424. if 'log_level' in data:
  425. util.get_logger().setLevel(data['log_level'])
  426. if 'sys_path' in data:
  427. sys.path = data['sys_path']
  428. if 'sys_argv' in data:
  429. sys.argv = data['sys_argv']
  430. if 'dir' in data:
  431. os.chdir(data['dir'])
  432. if 'orig_dir' in data:
  433. process.ORIGINAL_DIR = data['orig_dir']
  434. if 'main_path' in data:
  435. main_path = data['main_path']
  436. main_name = os.path.splitext(os.path.basename(main_path))[0]
  437. if main_name == '__init__':
  438. main_name = os.path.basename(os.path.dirname(main_path))
  439. if main_name == '__main__':
  440. main_module = sys.modules['__main__']
  441. main_module.__file__ = main_path
  442. elif main_name != 'ipython':
  443. # Main modules not actually called __main__.py may
  444. # contain additional code that should still be executed
  445. import imp
  446. if main_path is None:
  447. dirs = None
  448. elif os.path.basename(main_path).startswith('__init__.py'):
  449. dirs = [os.path.dirname(os.path.dirname(main_path))]
  450. else:
  451. dirs = [os.path.dirname(main_path)]
  452. assert main_name not in sys.modules, main_name
  453. file, path_name, etc = imp.find_module(main_name, dirs)
  454. try:
  455. # We would like to do "imp.load_module('__main__', ...)"
  456. # here. However, that would cause 'if __name__ ==
  457. # "__main__"' clauses to be executed.
  458. main_module = imp.load_module(
  459. '__parents_main__', file, path_name, etc
  460. )
  461. finally:
  462. if file:
  463. file.close()
  464. sys.modules['__main__'] = main_module
  465. main_module.__name__ = '__main__'
  466. # Try to make the potentially picklable objects in
  467. # sys.modules['__main__'] realize they are in the main
  468. # module -- somewhat ugly.
  469. for obj in list(main_module.__dict__.values()):
  470. try:
  471. if obj.__module__ == '__parents_main__':
  472. obj.__module__ = '__main__'
  473. except Exception:
  474. pass