aboutsummaryrefslogtreecommitdiff
blob: 1779534379b06470d535a29b01383d5cc724c18e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# Copyright 2018 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

import multiprocessing
import os

from portage.tests import TestCase
from portage.util._eventloop.global_event_loop import global_event_loop
from portage.util.futures import asyncio
from portage.util.futures.unix_events import DefaultEventLoopPolicy


def fork_main(parent_conn, child_conn):
	parent_conn.close()
	loop = asyncio._wrap_loop()
	# This fails with python's default event loop policy,
	# see https://bugs.python.org/issue22087.
	loop.run_until_complete(asyncio.sleep(0.1, loop=loop))
	loop.close()


def async_main(fork_exitcode, loop=None):
	loop = asyncio._wrap_loop(loop)

	# Since python2.7 does not support Process.sentinel, use Pipe to
	# monitor for process exit.
	parent_conn, child_conn = multiprocessing.Pipe()

	def eof_callback(proc):
		loop.remove_reader(parent_conn.fileno())
		parent_conn.close()
		proc.join()
		fork_exitcode.set_result(proc.exitcode)

	proc = multiprocessing.Process(target=fork_main, args=(parent_conn, child_conn))
	loop.add_reader(parent_conn.fileno(), eof_callback, proc)
	proc.start()
	child_conn.close()


class EventLoopInForkTestCase(TestCase):
	"""
	The default asyncio event loop policy does not support loops
	running in forks, see https://bugs.python.org/issue22087.
	Portage's DefaultEventLoopPolicy supports forks.
	"""

	def testEventLoopInForkTestCase(self):
		initial_policy = asyncio.get_event_loop_policy()
		if not isinstance(initial_policy, DefaultEventLoopPolicy):
			asyncio.set_event_loop_policy(DefaultEventLoopPolicy())
		loop = None
		try:
			loop = asyncio._wrap_loop()
			fork_exitcode = loop.create_future()
			# Make async_main fork while the loop is running, which would
			# trigger https://bugs.python.org/issue22087 with asyncio's
			# default event loop policy.
			loop.call_soon(async_main, fork_exitcode)
			assert loop.run_until_complete(fork_exitcode) == os.EX_OK
		finally:
			asyncio.set_event_loop_policy(initial_policy)
			if loop not in (None, global_event_loop()):
				loop.close()
				self.assertFalse(global_event_loop().is_closed())