commit df21cc14e22193bba5cd64f91fbd71a02bcd42c5
Author: Damian Johnson <atagar(a)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):
"""