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 and router, and responds to function calls identically to children
created using other methods. 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 For long-lived processes, :meth:`local` is always better as it
guarantees a pristine interpreter state that inherited little from the guarantees a pristine interpreter state that inherited little from the
parent. Forking should only be used in performance-sensitive scenarios 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 * Locks held in the parent causing random deadlocks in the child, such
as when another thread emits a log entry via the :mod:`logging` 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` * Objects existing in Thread-Local Storage of every non-:meth:`fork`
thread becoming permanently inaccessible, and never having their thread becoming permanently inaccessible, and never having their

@ -39,6 +39,18 @@ import mitogen.parent
LOG = logging.getLogger('mitogen') 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(): def fixup_prngs():
""" """
@ -113,9 +125,19 @@ class Stream(mitogen.parent.Stream):
#: User-supplied function for cleaning up child process state. #: User-supplied function for cleaning up child process state.
on_fork = None 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, def construct(self, old_router, max_message_size, on_fork=None,
debug=False, profiling=False, unidirectional=False, debug=False, profiling=False, unidirectional=False,
on_start=None): on_start=None):
if not FORK_SUPPORTED:
raise Error(self.python_version_msg)
# fork method only supports a tiny subset of options. # fork method only supports a tiny subset of options.
super(Stream, self).construct(max_message_size=max_message_size, super(Stream, self).construct(max_message_size=max_message_size,
debug=debug, profiling=profiling, debug=debug, profiling=profiling,

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

Loading…
Cancel
Save