[tor-commits] [stem/master] launch_tor() raisesd a ValueError when outside the main thread

atagar at torproject.org atagar at torproject.org
Sun Apr 2 21:41:52 UTC 2017


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



More information about the tor-commits mailing list