commit df21cc14e22193bba5cd64f91fbd71a02bcd42c5 Author: Damian Johnson atagar@torproject.org Date: Sun Apr 2 14:40:17 2017 -0700
launch_tor() raisesd a ValueError when outside the main thread
When outside the main thread signal raises...
ValueError: signal only works in main thread
To account for this doing two things...
* If the user doesn't specify a timeout then dropping it when outside the main thread to avoid erroring. This is similar to what we do on Windows (which doesn't support signal either).
* If the user explicitly specifies a timeout then providing a more useful error message.
Caught thanks to an anonymous user. --- docs/change_log.rst | 1 + stem/process.py | 13 +++++++++++-- test/integ/process.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst index 2c18560..d5c9755 100644 --- a/docs/change_log.rst +++ b/docs/change_log.rst @@ -45,6 +45,7 @@ The following are only available within Stem's `git repository
* **Controller**
+ * :func:`~stem.process.launch_tor` raised a ValueError if invoked when outside the main thread * Renamed :class:`~stem.response.events.ConnectionBandwidthEvent` type attribute to conn_type to avoid conflict with parent class (:trac:`21774`) * Added the GUARD_WAIT :data:`~stem.CircStatus` (:spec:`6446210`) * Unable to use cookie auth when path includes wide characters (chinese, japanese, etc) diff --git a/stem/process.py b/stem/process.py index ec10b21..3cab463 100644 --- a/stem/process.py +++ b/stem/process.py @@ -23,6 +23,7 @@ import re import signal import subprocess import tempfile +import threading
import stem.prereq import stem.util.str_tools @@ -47,8 +48,8 @@ def launch_tor(tor_cmd = 'tor', args = None, torrc_path = None, completion_perce default, but if you have a 'Log' entry in your torrc then you'll also need 'Log NOTICE stdout'.
- Note: The timeout argument does not work on Windows, and relies on the global - state of the signal module. + Note: The timeout argument does not work on Windows or when outside the + main thread, and relies on the global state of the signal module.
.. versionchanged:: 1.6.0 Allowing the timeout argument to be a float. @@ -74,6 +75,14 @@ def launch_tor(tor_cmd = 'tor', args = None, torrc_path = None, completion_perce """
if stem.util.system.is_windows(): + if timeout is not None and timeout != DEFAULT_INIT_TIMEOUT: + raise OSError('You cannot launch tor with a timeout on Windows') + + timeout = None + elif threading.current_thread().__class__.__name__ != '_MainThread': + if timeout is not None and timeout != DEFAULT_INIT_TIMEOUT: + raise OSError('Launching tor with a timeout can only be done in the main thread') + timeout = None
# sanity check that we got a tor binary diff --git a/test/integ/process.py b/test/integ/process.py index 3f7ac65..66f2018 100644 --- a/test/integ/process.py +++ b/test/integ/process.py @@ -9,6 +9,7 @@ import re import shutil import subprocess import tempfile +import threading import time import unittest
@@ -275,6 +276,49 @@ class TestProcess(unittest.TestCase): self.assertTrue('Configuration was valid' in output)
@only_run_once + def test_can_run_multithreaded(self): + """ + Our launch_tor() function uses signal to support its timeout argument. + This only works in the main thread so ensure we give a useful message when + it isn't. + """ + + # Tries running tor in another thread with the given timeout argument. This + # issues an invalid torrc so we terminate right away if we get to the point + # of actually invoking tor. + # + # Returns None if launching tor is successful, and otherwise returns the + # exception we raised. + + def launch_async_with_timeout(timeout_arg): + raised_exc, tor_cmd = [None], test.runner.get_runner().get_tor_command() + + def short_launch(): + try: + stem.process.launch_tor_with_config({'SocksPort': 'invalid', 'DataDirectory': self.data_directory}, tor_cmd, 100, None, timeout_arg) + except Exception as exc: + raised_exc[0] = exc + + t = threading.Thread(target = short_launch) + t.start() + t.join() + + if 'Invalid SocksPort/SocksListenAddress' in str(raised_exc[0]): + return None # got to the point of invoking tor + else: + return raised_exc[0] + + exc = launch_async_with_timeout(0.5) + self.assertEqual(OSError, type(exc)) + self.assertEqual('Launching tor with a timeout can only be done in the main thread', str(exc)) + + # We should launch successfully if no timeout is specified or we specify it + # to be 'None'. + + self.assertEqual(None, launch_async_with_timeout(None)) + self.assertEqual(None, launch_async_with_timeout(stem.process.DEFAULT_INIT_TIMEOUT)) + + @only_run_once @patch('stem.version.get_system_tor_version', Mock(return_value = stem.version.Version('0.0.0.1'))) def test_launch_tor_with_config_via_file(self): """
tor-commits@lists.torproject.org