issue #477: make mitogen.fork unsupported on Py<2.6.

issue510
David Wilson 6 years ago
parent 112caa94f9
commit 6a2f88d6a3

@ -115,6 +115,9 @@ Connection Methods
and router, and responds to function calls identically to children
created using other methods.
The use of this method is strongly discouraged. It requires Python 2.6 or
newer, as older Pythons made no effort to reset threading state upon fork.
For long-lived processes, :meth:`local` is always better as it
guarantees a pristine interpreter state that inherited little from the
parent. Forking should only be used in performance-sensitive scenarios
@ -158,7 +161,9 @@ Connection Methods
* Locks held in the parent causing random deadlocks in the child, such
as when another thread emits a log entry via the :mod:`logging`
package concurrent to another thread calling :meth:`fork`.
package concurrent to another thread calling :meth:`fork`, or when a C
extension module calls the C library allocator, or when a thread is using
the C library DNS resolver, for example via :func:`socket.gethostbyname`.
* Objects existing in Thread-Local Storage of every non-:meth:`fork`
thread becoming permanently inaccessible, and never having their

@ -39,6 +39,18 @@ import mitogen.parent
LOG = logging.getLogger('mitogen')
# Python 2.4/2.5 cannot support fork+threads whatsoever, it doesn't even fix up
# interpreter state. So 2.4/2.5 interpreters start .local() contexts for
# isolation instead. Since we don't have any crazy memory sharing problems to
# avoid, there is no virginal fork parent either. The child is started directly
# from the login/become process. In future this will be default everywhere,
# fork is brainwrong from the stone age.
FORK_SUPPORTED = sys.version_info >= (2, 6)
class Error(mitogen.core.StreamError):
pass
def fixup_prngs():
"""
@ -113,9 +125,19 @@ class Stream(mitogen.parent.Stream):
#: User-supplied function for cleaning up child process state.
on_fork = None
python_version_msg = (
"The mitogen.fork method is not supported on Python versions "
"prior to 2.6, since those versions made no attempt to repair "
"critical interpreter state following a fork. Please use the "
"local() method instead."
)
def construct(self, old_router, max_message_size, on_fork=None,
debug=False, profiling=False, unidirectional=False,
on_start=None):
if not FORK_SUPPORTED:
raise Error(self.python_version_msg)
# fork method only supports a tiny subset of options.
super(Stream, self).construct(max_message_size=max_message_size,
debug=debug, profiling=profiling,

@ -1,12 +1,25 @@
import _ssl
import ctypes
import os
import random
import ssl
import struct
import sys
try:
import _ssl
except ImportError:
_ssl = None
try:
import ssl
except ImportError:
ssl = None
try:
import ctypes
except ImportError:
# Python 2.4
ctypes = None
import mitogen
import unittest2
@ -29,13 +42,14 @@ def _find_ssl_darwin():
return bits[1]
if sys.platform.startswith('linux'):
if ctypes and sys.platform.startswith('linux'):
LIBSSL_PATH = _find_ssl_linux()
elif sys.platform == 'darwin':
elif ctypes and sys.platform == 'darwin':
LIBSSL_PATH = _find_ssl_darwin()
else:
assert 0, "Don't know how to find libssl on this platform"
LIBSSL_PATH = None
if ctypes and LIBSSL_PATH:
c_ssl = ctypes.CDLL(LIBSSL_PATH)
c_ssl.RAND_pseudo_bytes.argtypes = [ctypes.c_char_p, ctypes.c_int]
c_ssl.RAND_pseudo_bytes.restype = ctypes.c_int
@ -64,6 +78,12 @@ def exercise_importer(n):
return simple_pkg.a.subtract_one_add_two(n)
skipIfUnsupported = unittest2.skipIf(
condition=(not mitogen.fork.FORK_SUPPORTED),
reason="mitogen.fork unsupported on this platform"
)
class ForkTest(testlib.RouterMixin, testlib.TestCase):
def test_okay(self):
context = self.router.fork()
@ -74,6 +94,10 @@ class ForkTest(testlib.RouterMixin, testlib.TestCase):
context = self.router.fork()
self.assertNotEqual(context.call(random_random), random_random())
@unittest2.skipIf(
condition=LIBSSL_PATH is None or ctypes is None,
reason='cant test libssl on this platform',
)
def test_ssl_module_diverges(self):
# Ensure generator state is initialized.
RAND_pseudo_bytes()
@ -93,6 +117,8 @@ class ForkTest(testlib.RouterMixin, testlib.TestCase):
context = self.router.fork(on_start=on_start)
self.assertEquals(123, recv.get().unpickle())
ForkTest = skipIfUnsupported(ForkTest)
class DoubleChildTest(testlib.RouterMixin, testlib.TestCase):
def test_okay(self):
@ -115,6 +141,8 @@ class DoubleChildTest(testlib.RouterMixin, testlib.TestCase):
c2 = self.router.fork(name='c2', via=c1)
self.assertEqual(2, c2.call(exercise_importer, 1))
DoubleChildTest = skipIfUnsupported(DoubleChildTest)
if __name__ == '__main__':
unittest2.main()

Loading…
Cancel
Save