Repository: enthought/traits-futures

Issue:
Default number of workers in the `TraitsExecutor` should be configurable
We have a hard-coded `max_workers=4` where the `TraitsExecutor` creates its underlying `concurrent.futures.ThreadPoolExecutor`. That's wrong!

At the very least, that `4` should be a module-level constant, but it would be better to provide a way to create a `TraitsExecutor` that:

- owns its underlying executor
- allows the number of threads in that executor to be set in advance


Discussion:

Patch:
diff --git a/traits_futures/traits_executor.py b/traits_futures/traits_executor.py
index 2cdb02d..04d3fd0 100644
--- a/traits_futures/traits_executor.py
+++ b/traits_futures/traits_executor.py
@@ -46,6 +46,20 @@ def _background_job_wrapper(background_job, sender):
 class TraitsExecutor(HasStrictTraits):
     """
     Executor to initiate and manage background tasks.
+
+    Parameters
+    ----------
+    thread_pool : concurrent.futures.ThreadPoolExecutor, optional
+        If supplied, provides the underlying thread pool executor to use. In
+        this case, the creator of the TraitsExecutor is responsible for
+        shutting down the thread pool once it's no longer needed. If not
+        supplied, a new private thread pool will be created, and this object's
+        ``stop`` method will shut down that thread pool.
+    max_workers : int or None, optional
+        Maximum number of workers for the private thread pool. This parameter
+        is mutually exclusive with thread_pool. The default is ``None``, which
+        delegates the choice of number of workers to Python's
+        ``ThreadPoolExecutor``.
     """
     #: Current state of this executor.
     state = ExecutorState
@@ -58,14 +72,18 @@ class TraitsExecutor(HasStrictTraits):
     #: to dispose of related resources (like the thread pool).
     stopped = Property(Bool())
 
-    def __init__(self, thread_pool=None, **traits):
+    def __init__(self, thread_pool=None, max_workers=None, **traits):
         super(TraitsExecutor, self).__init__(**traits)
 
         if thread_pool is None:
             self._thread_pool = concurrent.futures.ThreadPoolExecutor(
-                max_workers=4)
+                max_workers=max_workers)
             self._own_thread_pool = True
         else:
+            if max_workers is not None:
+                raise TypeError(
+                    "at most one of 'thread_pool' and 'max_workers' "
+                    "should be supplied")
             self._thread_pool = thread_pool
             self._own_thread_pool = False
 


Test patch:
diff --git a/traits_futures/tests/test_traits_executor.py b/traits_futures/tests/test_traits_executor.py
index 4a70743..1ad46d4 100644
--- a/traits_futures/tests/test_traits_executor.py
+++ b/traits_futures/tests/test_traits_executor.py
@@ -100,6 +100,17 @@ class TestTraitsExecutor(GuiTestAssistant, unittest.TestCase):
             del self.executor
         GuiTestAssistant.tearDown(self)
 
+    def test_max_workers(self):
+        executor = TraitsExecutor(max_workers=11)
+        self.assertEqual(executor._thread_pool._max_workers, 11)
+        executor.stop()
+        self.wait_until_stopped(executor)
+
+    def test_max_workers_mutually_exclusive_with_thread_pool(self):
+        with self.temporary_thread_pool() as thread_pool:
+            with self.assertRaises(TypeError):
+                TraitsExecutor(thread_pool=thread_pool, max_workers=11)
+
     def test_stop_method(self):
         executor = TraitsExecutor()
         listener = ExecutorListener(executor=executor)