Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions executorlib/base/executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,14 @@ def __init__(self, max_cores: Optional[int] = None):
self._future_queue: Optional[queue.Queue] = queue.Queue()
self._process: Optional[Union[Thread, list[Thread]]] = None

@property
def max_workers(self) -> Optional[int]:
return self._process_kwargs.get("max_workers")

@max_workers.setter
def max_workers(self, max_workers: int):
raise NotImplementedError("The max_workers setter is not implemented.")

@property
def info(self) -> Optional[dict]:
"""
Expand Down
35 changes: 33 additions & 2 deletions executorlib/interactive/blockallocation.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import queue
from concurrent.futures import Future
from threading import Thread
from typing import Callable, Optional
Expand DownExpand Up@@ -27,7 +28,7 @@ class BlockAllocationExecutor(ExecutorBase):
Examples:

>>> import numpy as np
>>> from executorlib.interactive.shared import BlockAllocationExecutor
>>> from executorlib.interactive.blockallocation import BlockAllocationExecutor
>>>
>>> def calc(i, j, k):
>>> from mpi4py import MPI
Expand DownExpand Up@@ -58,16 +59,46 @@ def __init__(
executor_kwargs["spawner"] = spawner
executor_kwargs["queue_join_on_shutdown"] = False
self._process_kwargs = executor_kwargs
self._max_workers = max_workers
self._set_process(
process=[
Thread(
target=execute_tasks,
kwargs=executor_kwargs,
)
for _ in range(max_workers)
for _ in range(self._max_workers)
],
)

@property
def max_workers(self) -> int:
return self._max_workers

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue) and isinstance(
self._process, list
):
if self._max_workers > max_workers:
for _ in range(self._max_workers - max_workers):
self._future_queue.queue.insert(0, {"shutdown": True, "wait": True})
while len(self._process) > max_workers:
self._process = [
process for process in self._process if process.is_alive()
]
elif self._max_workers < max_workers:
new_process_lst = [
Thread(
target=execute_tasks,
kwargs=self._process_kwargs,
)
for _ in range(max_workers - self._max_workers)
]
for process_instance in new_process_lst:
process_instance.start()
self._process += new_process_lst
self._max_workers = max_workers

def submit( # type: ignore
self, fn: Callable, *args, resource_dict: Optional[dict] = None, **kwargs
) -> Future:
Expand Down
40 changes: 38 additions & 2 deletions executorlib/interactive/dependency.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,12 +76,39 @@ def info(self) -> Optional[dict]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "info", "future": f}
0, {"internal": True, "task": "get_info", "future": f}
)
return f.result()
else:
return None

@property
def max_workers(self) -> Optional[int]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "get_max_workers", "future": f}
)
return f.result()
else:
return None

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0,
{
"internal": True,
"task": "set_max_workers",
"max_workers": max_workers,
"future": f,
},
)
if not f.result():
raise NotImplementedError("The max_workers setter is not implemented.")

def submit( # type: ignore
self,
fn: Callable[..., Any],
Expand DownExpand Up@@ -188,8 +215,17 @@ def _execute_tasks_with_dependencies(
if ( # shutdown the executor
task_dict is not None and "internal" in task_dict and task_dict["internal"]
):
if task_dict["task"] == "info":
if task_dict["task"] == "get_info":
task_dict["future"].set_result(executor.info)
elif task_dict["task"] == "get_max_workers":
task_dict["future"].set_result(executor.max_workers)
elif task_dict["task"] == "set_max_workers":
try:
executor.max_workers = task_dict["max_workers"]
except NotImplementedError:
task_dict["future"].set_result(False)
else:
task_dict["future"].set_result(True)
elif ( # handle function submitted to the executor
task_dict is not None and "fn" in task_dict and "future" in task_dict
):
Expand Down
16 changes: 11 additions & 5 deletions executorlib/interactive/shared.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import contextlib
import importlib.util
import os
import queue
Expand DownExpand Up@@ -57,7 +58,7 @@ def execute_tasks(
task_dict = future_queue.get()
if "shutdown" in task_dict and task_dict["shutdown"]:
interface.shutdown(wait=task_dict["wait"])
future_queue.task_done()
_task_done(future_queue=future_queue)
if queue_join_on_shutdown:
future_queue.join()
break
Expand DownExpand Up@@ -117,10 +118,10 @@ def _execute_task_without_cache(
f.set_result(interface.send_and_receive_dict(input_dict=task_dict))
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
else:
future_queue.task_done()
_task_done(future_queue=future_queue)


def _execute_task_with_cache(
Expand DownExpand Up@@ -161,13 +162,18 @@ def _execute_task_with_cache(
f.set_result(result)
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
raise thread_exception
else:
future_queue.task_done()
_task_done(future_queue=future_queue)
else:
_, result = get_output(file_name=file_name)
future = task_dict["future"]
future.set_result(result)
_task_done(future_queue=future_queue)


def _task_done(future_queue: queue.Queue):
with contextlib.suppress(ValueError):
future_queue.task_done()
16 changes: 16 additions & 0 deletions tests/test_local_executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,6 +77,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with BlockAllocationExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with BlockAllocationExecutor(
max_workers=1,
Expand DownExpand Up@@ -107,6 +115,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with OneTaskPerProcessExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with OneTaskPerProcessExecutor(
max_cores=1,
Expand Down
80 changes: 80 additions & 0 deletions tests/test_local_executor_resize.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
import unittest
from executorlib import SingleNodeExecutor
from executorlib.standalone.serialize import cloudpickle_register


def sleep_funct(sec):
from time import sleep
sleep(sec)
return sec


class TestResizing(unittest.TestCase):
def test_without_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 1)
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_without_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_no_block_allocation(self):
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=False) as exe:
exe.max_workers = 2
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=True) as exe:
exe.max_workers = 2

def test_max_workers_stopped_executor(self):
exe = SingleNodeExecutor(block_allocation=True)
exe.shutdown(wait=True)
self.assertIsNone(exe.max_workers)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions executorlib/base/executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,14 @@ def __init__(self, max_cores: Optional[int] = None):
self._future_queue: Optional[queue.Queue] = queue.Queue()
self._process: Optional[Union[Thread, list[Thread]]] = None

@property
def max_workers(self) -> Optional[int]:
return self._process_kwargs.get("max_workers")

@max_workers.setter
def max_workers(self, max_workers: int):
raise NotImplementedError("The max_workers setter is not implemented.")

@property
def info(self) -> Optional[dict]:
"""
Expand Down
35 changes: 33 additions & 2 deletions executorlib/interactive/blockallocation.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import queue
from concurrent.futures import Future
from threading import Thread
from typing import Callable, Optional
Expand DownExpand Up@@ -27,7 +28,7 @@ class BlockAllocationExecutor(ExecutorBase):
Examples:

>>> import numpy as np
>>> from executorlib.interactive.shared import BlockAllocationExecutor
>>> from executorlib.interactive.blockallocation import BlockAllocationExecutor
>>>
>>> def calc(i, j, k):
>>> from mpi4py import MPI
Expand DownExpand Up@@ -58,16 +59,46 @@ def __init__(
executor_kwargs["spawner"] = spawner
executor_kwargs["queue_join_on_shutdown"] = False
self._process_kwargs = executor_kwargs
self._max_workers = max_workers
self._set_process(
process=[
Thread(
target=execute_tasks,
kwargs=executor_kwargs,
)
for _ in range(max_workers)
for _ in range(self._max_workers)
],
)

@property
def max_workers(self) -> int:
return self._max_workers

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue) and isinstance(
self._process, list
):
if self._max_workers > max_workers:
for _ in range(self._max_workers - max_workers):
self._future_queue.queue.insert(0, {"shutdown": True, "wait": True})
while len(self._process) > max_workers:
self._process = [
process for process in self._process if process.is_alive()
]
elif self._max_workers < max_workers:
new_process_lst = [
Thread(
target=execute_tasks,
kwargs=self._process_kwargs,
)
for _ in range(max_workers - self._max_workers)
]
for process_instance in new_process_lst:
process_instance.start()
self._process += new_process_lst
self._max_workers = max_workers

def submit( # type: ignore
self, fn: Callable, *args, resource_dict: Optional[dict] = None, **kwargs
) -> Future:
Expand Down
40 changes: 38 additions & 2 deletions executorlib/interactive/dependency.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,12 +76,39 @@ def info(self) -> Optional[dict]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "info", "future": f}
0, {"internal": True, "task": "get_info", "future": f}
)
return f.result()
else:
return None

@property
def max_workers(self) -> Optional[int]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "get_max_workers", "future": f}
)
return f.result()
else:
return None

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0,
{
"internal": True,
"task": "set_max_workers",
"max_workers": max_workers,
"future": f,
},
)
if not f.result():
raise NotImplementedError("The max_workers setter is not implemented.")

def submit( # type: ignore
self,
fn: Callable[..., Any],
Expand DownExpand Up@@ -188,8 +215,17 @@ def _execute_tasks_with_dependencies(
if ( # shutdown the executor
task_dict is not None and "internal" in task_dict and task_dict["internal"]
):
if task_dict["task"] == "info":
if task_dict["task"] == "get_info":
task_dict["future"].set_result(executor.info)
elif task_dict["task"] == "get_max_workers":
task_dict["future"].set_result(executor.max_workers)
elif task_dict["task"] == "set_max_workers":
try:
executor.max_workers = task_dict["max_workers"]
except NotImplementedError:
task_dict["future"].set_result(False)
else:
task_dict["future"].set_result(True)
elif ( # handle function submitted to the executor
task_dict is not None and "fn" in task_dict and "future" in task_dict
):
Expand Down
16 changes: 11 additions & 5 deletions executorlib/interactive/shared.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import contextlib
import importlib.util
import os
import queue
Expand DownExpand Up@@ -57,7 +58,7 @@ def execute_tasks(
task_dict = future_queue.get()
if "shutdown" in task_dict and task_dict["shutdown"]:
interface.shutdown(wait=task_dict["wait"])
future_queue.task_done()
_task_done(future_queue=future_queue)
if queue_join_on_shutdown:
future_queue.join()
break
Expand DownExpand Up@@ -117,10 +118,10 @@ def _execute_task_without_cache(
f.set_result(interface.send_and_receive_dict(input_dict=task_dict))
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
else:
future_queue.task_done()
_task_done(future_queue=future_queue)


def _execute_task_with_cache(
Expand DownExpand Up@@ -161,13 +162,18 @@ def _execute_task_with_cache(
f.set_result(result)
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
raise thread_exception
else:
future_queue.task_done()
_task_done(future_queue=future_queue)
else:
_, result = get_output(file_name=file_name)
future = task_dict["future"]
future.set_result(result)
_task_done(future_queue=future_queue)


def _task_done(future_queue: queue.Queue):
with contextlib.suppress(ValueError):
future_queue.task_done()
16 changes: 16 additions & 0 deletions tests/test_local_executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,6 +77,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with BlockAllocationExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with BlockAllocationExecutor(
max_workers=1,
Expand DownExpand Up@@ -107,6 +115,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with OneTaskPerProcessExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with OneTaskPerProcessExecutor(
max_cores=1,
Expand Down
80 changes: 80 additions & 0 deletions tests/test_local_executor_resize.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
import unittest
from executorlib import SingleNodeExecutor
from executorlib.standalone.serialize import cloudpickle_register


def sleep_funct(sec):
from time import sleep
sleep(sec)
return sec


class TestResizing(unittest.TestCase):
def test_without_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 1)
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_without_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_no_block_allocation(self):
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=False) as exe:
exe.max_workers = 2
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=True) as exe:
exe.max_workers = 2

def test_max_workers_stopped_executor(self):
exe = SingleNodeExecutor(block_allocation=True)
exe.shutdown(wait=True)
self.assertIsNone(exe.max_workers)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions executorlib/base/executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,14 @@ def __init__(self, max_cores: Optional[int] = None):
self._future_queue: Optional[queue.Queue] = queue.Queue()
self._process: Optional[Union[Thread, list[Thread]]] = None

@property
def max_workers(self) -> Optional[int]:
return self._process_kwargs.get("max_workers")

@max_workers.setter
def max_workers(self, max_workers: int):
raise NotImplementedError("The max_workers setter is not implemented.")

@property
def info(self) -> Optional[dict]:
"""
Expand Down
35 changes: 33 additions & 2 deletions executorlib/interactive/blockallocation.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import queue
from concurrent.futures import Future
from threading import Thread
from typing import Callable, Optional
Expand DownExpand Up@@ -27,7 +28,7 @@ class BlockAllocationExecutor(ExecutorBase):
Examples:

>>> import numpy as np
>>> from executorlib.interactive.shared import BlockAllocationExecutor
>>> from executorlib.interactive.blockallocation import BlockAllocationExecutor
>>>
>>> def calc(i, j, k):
>>> from mpi4py import MPI
Expand DownExpand Up@@ -58,16 +59,46 @@ def __init__(
executor_kwargs["spawner"] = spawner
executor_kwargs["queue_join_on_shutdown"] = False
self._process_kwargs = executor_kwargs
self._max_workers = max_workers
self._set_process(
process=[
Thread(
target=execute_tasks,
kwargs=executor_kwargs,
)
for _ in range(max_workers)
for _ in range(self._max_workers)
],
)

@property
def max_workers(self) -> int:
return self._max_workers

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue) and isinstance(
self._process, list
):
if self._max_workers > max_workers:
for _ in range(self._max_workers - max_workers):
self._future_queue.queue.insert(0, {"shutdown": True, "wait": True})
while len(self._process) > max_workers:
self._process = [
process for process in self._process if process.is_alive()
]
elif self._max_workers < max_workers:
new_process_lst = [
Thread(
target=execute_tasks,
kwargs=self._process_kwargs,
)
for _ in range(max_workers - self._max_workers)
]
for process_instance in new_process_lst:
process_instance.start()
self._process += new_process_lst
self._max_workers = max_workers

def submit( # type: ignore
self, fn: Callable, *args, resource_dict: Optional[dict] = None, **kwargs
) -> Future:
Expand Down
40 changes: 38 additions & 2 deletions executorlib/interactive/dependency.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,12 +76,39 @@ def info(self) -> Optional[dict]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "info", "future": f}
0, {"internal": True, "task": "get_info", "future": f}
)
return f.result()
else:
return None

@property
def max_workers(self) -> Optional[int]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "get_max_workers", "future": f}
)
return f.result()
else:
return None

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0,
{
"internal": True,
"task": "set_max_workers",
"max_workers": max_workers,
"future": f,
},
)
if not f.result():
raise NotImplementedError("The max_workers setter is not implemented.")

def submit( # type: ignore
self,
fn: Callable[..., Any],
Expand DownExpand Up@@ -188,8 +215,17 @@ def _execute_tasks_with_dependencies(
if ( # shutdown the executor
task_dict is not None and "internal" in task_dict and task_dict["internal"]
):
if task_dict["task"] == "info":
if task_dict["task"] == "get_info":
task_dict["future"].set_result(executor.info)
elif task_dict["task"] == "get_max_workers":
task_dict["future"].set_result(executor.max_workers)
elif task_dict["task"] == "set_max_workers":
try:
executor.max_workers = task_dict["max_workers"]
except NotImplementedError:
task_dict["future"].set_result(False)
else:
task_dict["future"].set_result(True)
elif ( # handle function submitted to the executor
task_dict is not None and "fn" in task_dict and "future" in task_dict
):
Expand Down
16 changes: 11 additions & 5 deletions executorlib/interactive/shared.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import contextlib
import importlib.util
import os
import queue
Expand DownExpand Up@@ -57,7 +58,7 @@ def execute_tasks(
task_dict = future_queue.get()
if "shutdown" in task_dict and task_dict["shutdown"]:
interface.shutdown(wait=task_dict["wait"])
future_queue.task_done()
_task_done(future_queue=future_queue)
if queue_join_on_shutdown:
future_queue.join()
break
Expand DownExpand Up@@ -117,10 +118,10 @@ def _execute_task_without_cache(
f.set_result(interface.send_and_receive_dict(input_dict=task_dict))
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
else:
future_queue.task_done()
_task_done(future_queue=future_queue)


def _execute_task_with_cache(
Expand DownExpand Up@@ -161,13 +162,18 @@ def _execute_task_with_cache(
f.set_result(result)
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
raise thread_exception
else:
future_queue.task_done()
_task_done(future_queue=future_queue)
else:
_, result = get_output(file_name=file_name)
future = task_dict["future"]
future.set_result(result)
_task_done(future_queue=future_queue)


def _task_done(future_queue: queue.Queue):
with contextlib.suppress(ValueError):
future_queue.task_done()
16 changes: 16 additions & 0 deletions tests/test_local_executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,6 +77,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with BlockAllocationExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with BlockAllocationExecutor(
max_workers=1,
Expand DownExpand Up@@ -107,6 +115,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with OneTaskPerProcessExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with OneTaskPerProcessExecutor(
max_cores=1,
Expand Down
80 changes: 80 additions & 0 deletions tests/test_local_executor_resize.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
import unittest
from executorlib import SingleNodeExecutor
from executorlib.standalone.serialize import cloudpickle_register


def sleep_funct(sec):
from time import sleep
sleep(sec)
return sec


class TestResizing(unittest.TestCase):
def test_without_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 1)
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_without_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_no_block_allocation(self):
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=False) as exe:
exe.max_workers = 2
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=True) as exe:
exe.max_workers = 2

def test_max_workers_stopped_executor(self):
exe = SingleNodeExecutor(block_allocation=True)
exe.shutdown(wait=True)
self.assertIsNone(exe.max_workers)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions executorlib/base/executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,14 @@ def __init__(self, max_cores: Optional[int] = None):
self._future_queue: Optional[queue.Queue] = queue.Queue()
self._process: Optional[Union[Thread, list[Thread]]] = None

@property
def max_workers(self) -> Optional[int]:
return self._process_kwargs.get("max_workers")

@max_workers.setter
def max_workers(self, max_workers: int):
raise NotImplementedError("The max_workers setter is not implemented.")

@property
def info(self) -> Optional[dict]:
"""
Expand Down
35 changes: 33 additions & 2 deletions executorlib/interactive/blockallocation.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import queue
from concurrent.futures import Future
from threading import Thread
from typing import Callable, Optional
Expand DownExpand Up@@ -27,7 +28,7 @@ class BlockAllocationExecutor(ExecutorBase):
Examples:

>>> import numpy as np
>>> from executorlib.interactive.shared import BlockAllocationExecutor
>>> from executorlib.interactive.blockallocation import BlockAllocationExecutor
>>>
>>> def calc(i, j, k):
>>> from mpi4py import MPI
Expand DownExpand Up@@ -58,16 +59,46 @@ def __init__(
executor_kwargs["spawner"] = spawner
executor_kwargs["queue_join_on_shutdown"] = False
self._process_kwargs = executor_kwargs
self._max_workers = max_workers
self._set_process(
process=[
Thread(
target=execute_tasks,
kwargs=executor_kwargs,
)
for _ in range(max_workers)
for _ in range(self._max_workers)
],
)

@property
def max_workers(self) -> int:
return self._max_workers

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue) and isinstance(
self._process, list
):
if self._max_workers > max_workers:
for _ in range(self._max_workers - max_workers):
self._future_queue.queue.insert(0, {"shutdown": True, "wait": True})
while len(self._process) > max_workers:
self._process = [
process for process in self._process if process.is_alive()
]
elif self._max_workers < max_workers:
new_process_lst = [
Thread(
target=execute_tasks,
kwargs=self._process_kwargs,
)
for _ in range(max_workers - self._max_workers)
]
for process_instance in new_process_lst:
process_instance.start()
self._process += new_process_lst
self._max_workers = max_workers

def submit( # type: ignore
self, fn: Callable, *args, resource_dict: Optional[dict] = None, **kwargs
) -> Future:
Expand Down
40 changes: 38 additions & 2 deletions executorlib/interactive/dependency.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,12 +76,39 @@ def info(self) -> Optional[dict]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "info", "future": f}
0, {"internal": True, "task": "get_info", "future": f}
)
return f.result()
else:
return None

@property
def max_workers(self) -> Optional[int]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "get_max_workers", "future": f}
)
return f.result()
else:
return None

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0,
{
"internal": True,
"task": "set_max_workers",
"max_workers": max_workers,
"future": f,
},
)
if not f.result():
raise NotImplementedError("The max_workers setter is not implemented.")

def submit( # type: ignore
self,
fn: Callable[..., Any],
Expand DownExpand Up@@ -188,8 +215,17 @@ def _execute_tasks_with_dependencies(
if ( # shutdown the executor
task_dict is not None and "internal" in task_dict and task_dict["internal"]
):
if task_dict["task"] == "info":
if task_dict["task"] == "get_info":
task_dict["future"].set_result(executor.info)
elif task_dict["task"] == "get_max_workers":
task_dict["future"].set_result(executor.max_workers)
elif task_dict["task"] == "set_max_workers":
try:
executor.max_workers = task_dict["max_workers"]
except NotImplementedError:
task_dict["future"].set_result(False)
else:
task_dict["future"].set_result(True)
elif ( # handle function submitted to the executor
task_dict is not None and "fn" in task_dict and "future" in task_dict
):
Expand Down
16 changes: 11 additions & 5 deletions executorlib/interactive/shared.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import contextlib
import importlib.util
import os
import queue
Expand DownExpand Up@@ -57,7 +58,7 @@ def execute_tasks(
task_dict = future_queue.get()
if "shutdown" in task_dict and task_dict["shutdown"]:
interface.shutdown(wait=task_dict["wait"])
future_queue.task_done()
_task_done(future_queue=future_queue)
if queue_join_on_shutdown:
future_queue.join()
break
Expand DownExpand Up@@ -117,10 +118,10 @@ def _execute_task_without_cache(
f.set_result(interface.send_and_receive_dict(input_dict=task_dict))
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
else:
future_queue.task_done()
_task_done(future_queue=future_queue)


def _execute_task_with_cache(
Expand DownExpand Up@@ -161,13 +162,18 @@ def _execute_task_with_cache(
f.set_result(result)
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
raise thread_exception
else:
future_queue.task_done()
_task_done(future_queue=future_queue)
else:
_, result = get_output(file_name=file_name)
future = task_dict["future"]
future.set_result(result)
_task_done(future_queue=future_queue)


def _task_done(future_queue: queue.Queue):
with contextlib.suppress(ValueError):
future_queue.task_done()
16 changes: 16 additions & 0 deletions tests/test_local_executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,6 +77,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with BlockAllocationExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with BlockAllocationExecutor(
max_workers=1,
Expand DownExpand Up@@ -107,6 +115,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with OneTaskPerProcessExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with OneTaskPerProcessExecutor(
max_cores=1,
Expand Down
80 changes: 80 additions & 0 deletions tests/test_local_executor_resize.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
import unittest
from executorlib import SingleNodeExecutor
from executorlib.standalone.serialize import cloudpickle_register


def sleep_funct(sec):
from time import sleep
sleep(sec)
return sec


class TestResizing(unittest.TestCase):
def test_without_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 1)
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_without_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_no_block_allocation(self):
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=False) as exe:
exe.max_workers = 2
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=True) as exe:
exe.max_workers = 2

def test_max_workers_stopped_executor(self):
exe = SingleNodeExecutor(block_allocation=True)
exe.shutdown(wait=True)
self.assertIsNone(exe.max_workers)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions executorlib/base/executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,14 @@ def __init__(self, max_cores: Optional[int] = None):
self._future_queue: Optional[queue.Queue] = queue.Queue()
self._process: Optional[Union[Thread, list[Thread]]] = None

@property
def max_workers(self) -> Optional[int]:
return self._process_kwargs.get("max_workers")

@max_workers.setter
def max_workers(self, max_workers: int):
raise NotImplementedError("The max_workers setter is not implemented.")

@property
def info(self) -> Optional[dict]:
"""
Expand Down
35 changes: 33 additions & 2 deletions executorlib/interactive/blockallocation.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import queue
from concurrent.futures import Future
from threading import Thread
from typing import Callable, Optional
Expand DownExpand Up@@ -27,7 +28,7 @@ class BlockAllocationExecutor(ExecutorBase):
Examples:

>>> import numpy as np
>>> from executorlib.interactive.shared import BlockAllocationExecutor
>>> from executorlib.interactive.blockallocation import BlockAllocationExecutor
>>>
>>> def calc(i, j, k):
>>> from mpi4py import MPI
Expand DownExpand Up@@ -58,16 +59,46 @@ def __init__(
executor_kwargs["spawner"] = spawner
executor_kwargs["queue_join_on_shutdown"] = False
self._process_kwargs = executor_kwargs
self._max_workers = max_workers
self._set_process(
process=[
Thread(
target=execute_tasks,
kwargs=executor_kwargs,
)
for _ in range(max_workers)
for _ in range(self._max_workers)
],
)

@property
def max_workers(self) -> int:
return self._max_workers

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue) and isinstance(
self._process, list
):
if self._max_workers > max_workers:
for _ in range(self._max_workers - max_workers):
self._future_queue.queue.insert(0, {"shutdown": True, "wait": True})
while len(self._process) > max_workers:
self._process = [
process for process in self._process if process.is_alive()
]
elif self._max_workers < max_workers:
new_process_lst = [
Thread(
target=execute_tasks,
kwargs=self._process_kwargs,
)
for _ in range(max_workers - self._max_workers)
]
for process_instance in new_process_lst:
process_instance.start()
self._process += new_process_lst
self._max_workers = max_workers

def submit( # type: ignore
self, fn: Callable, *args, resource_dict: Optional[dict] = None, **kwargs
) -> Future:
Expand Down
40 changes: 38 additions & 2 deletions executorlib/interactive/dependency.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,12 +76,39 @@ def info(self) -> Optional[dict]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "info", "future": f}
0, {"internal": True, "task": "get_info", "future": f}
)
return f.result()
else:
return None

@property
def max_workers(self) -> Optional[int]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "get_max_workers", "future": f}
)
return f.result()
else:
return None

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0,
{
"internal": True,
"task": "set_max_workers",
"max_workers": max_workers,
"future": f,
},
)
if not f.result():
raise NotImplementedError("The max_workers setter is not implemented.")

def submit( # type: ignore
self,
fn: Callable[..., Any],
Expand DownExpand Up@@ -188,8 +215,17 @@ def _execute_tasks_with_dependencies(
if ( # shutdown the executor
task_dict is not None and "internal" in task_dict and task_dict["internal"]
):
if task_dict["task"] == "info":
if task_dict["task"] == "get_info":
task_dict["future"].set_result(executor.info)
elif task_dict["task"] == "get_max_workers":
task_dict["future"].set_result(executor.max_workers)
elif task_dict["task"] == "set_max_workers":
try:
executor.max_workers = task_dict["max_workers"]
except NotImplementedError:
task_dict["future"].set_result(False)
else:
task_dict["future"].set_result(True)
elif ( # handle function submitted to the executor
task_dict is not None and "fn" in task_dict and "future" in task_dict
):
Expand Down
16 changes: 11 additions & 5 deletions executorlib/interactive/shared.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import contextlib
import importlib.util
import os
import queue
Expand DownExpand Up@@ -57,7 +58,7 @@ def execute_tasks(
task_dict = future_queue.get()
if "shutdown" in task_dict and task_dict["shutdown"]:
interface.shutdown(wait=task_dict["wait"])
future_queue.task_done()
_task_done(future_queue=future_queue)
if queue_join_on_shutdown:
future_queue.join()
break
Expand DownExpand Up@@ -117,10 +118,10 @@ def _execute_task_without_cache(
f.set_result(interface.send_and_receive_dict(input_dict=task_dict))
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
else:
future_queue.task_done()
_task_done(future_queue=future_queue)


def _execute_task_with_cache(
Expand DownExpand Up@@ -161,13 +162,18 @@ def _execute_task_with_cache(
f.set_result(result)
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
raise thread_exception
else:
future_queue.task_done()
_task_done(future_queue=future_queue)
else:
_, result = get_output(file_name=file_name)
future = task_dict["future"]
future.set_result(result)
_task_done(future_queue=future_queue)


def _task_done(future_queue: queue.Queue):
with contextlib.suppress(ValueError):
future_queue.task_done()
16 changes: 16 additions & 0 deletions tests/test_local_executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,6 +77,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with BlockAllocationExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with BlockAllocationExecutor(
max_workers=1,
Expand DownExpand Up@@ -107,6 +115,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with OneTaskPerProcessExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with OneTaskPerProcessExecutor(
max_cores=1,
Expand Down
80 changes: 80 additions & 0 deletions tests/test_local_executor_resize.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
import unittest
from executorlib import SingleNodeExecutor
from executorlib.standalone.serialize import cloudpickle_register


def sleep_funct(sec):
from time import sleep
sleep(sec)
return sec


class TestResizing(unittest.TestCase):
def test_without_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 1)
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_without_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_no_block_allocation(self):
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=False) as exe:
exe.max_workers = 2
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=True) as exe:
exe.max_workers = 2

def test_max_workers_stopped_executor(self):
exe = SingleNodeExecutor(block_allocation=True)
exe.shutdown(wait=True)
self.assertIsNone(exe.max_workers)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions executorlib/base/executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,14 @@ def __init__(self, max_cores: Optional[int] = None):
self._future_queue: Optional[queue.Queue] = queue.Queue()
self._process: Optional[Union[Thread, list[Thread]]] = None

@property
def max_workers(self) -> Optional[int]:
return self._process_kwargs.get("max_workers")

@max_workers.setter
def max_workers(self, max_workers: int):
raise NotImplementedError("The max_workers setter is not implemented.")

@property
def info(self) -> Optional[dict]:
"""
Expand Down
35 changes: 33 additions & 2 deletions executorlib/interactive/blockallocation.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import queue
from concurrent.futures import Future
from threading import Thread
from typing import Callable, Optional
Expand DownExpand Up@@ -27,7 +28,7 @@ class BlockAllocationExecutor(ExecutorBase):
Examples:

>>> import numpy as np
>>> from executorlib.interactive.shared import BlockAllocationExecutor
>>> from executorlib.interactive.blockallocation import BlockAllocationExecutor
>>>
>>> def calc(i, j, k):
>>> from mpi4py import MPI
Expand DownExpand Up@@ -58,16 +59,46 @@ def __init__(
executor_kwargs["spawner"] = spawner
executor_kwargs["queue_join_on_shutdown"] = False
self._process_kwargs = executor_kwargs
self._max_workers = max_workers
self._set_process(
process=[
Thread(
target=execute_tasks,
kwargs=executor_kwargs,
)
for _ in range(max_workers)
for _ in range(self._max_workers)
],
)

@property
def max_workers(self) -> int:
return self._max_workers

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue) and isinstance(
self._process, list
):
if self._max_workers > max_workers:
for _ in range(self._max_workers - max_workers):
self._future_queue.queue.insert(0, {"shutdown": True, "wait": True})
while len(self._process) > max_workers:
self._process = [
process for process in self._process if process.is_alive()
]
elif self._max_workers < max_workers:
new_process_lst = [
Thread(
target=execute_tasks,
kwargs=self._process_kwargs,
)
for _ in range(max_workers - self._max_workers)
]
for process_instance in new_process_lst:
process_instance.start()
self._process += new_process_lst
self._max_workers = max_workers

def submit( # type: ignore
self, fn: Callable, *args, resource_dict: Optional[dict] = None, **kwargs
) -> Future:
Expand Down
40 changes: 38 additions & 2 deletions executorlib/interactive/dependency.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,12 +76,39 @@ def info(self) -> Optional[dict]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "info", "future": f}
0, {"internal": True, "task": "get_info", "future": f}
)
return f.result()
else:
return None

@property
def max_workers(self) -> Optional[int]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "get_max_workers", "future": f}
)
return f.result()
else:
return None

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0,
{
"internal": True,
"task": "set_max_workers",
"max_workers": max_workers,
"future": f,
},
)
if not f.result():
raise NotImplementedError("The max_workers setter is not implemented.")

def submit( # type: ignore
self,
fn: Callable[..., Any],
Expand DownExpand Up@@ -188,8 +215,17 @@ def _execute_tasks_with_dependencies(
if ( # shutdown the executor
task_dict is not None and "internal" in task_dict and task_dict["internal"]
):
if task_dict["task"] == "info":
if task_dict["task"] == "get_info":
task_dict["future"].set_result(executor.info)
elif task_dict["task"] == "get_max_workers":
task_dict["future"].set_result(executor.max_workers)
elif task_dict["task"] == "set_max_workers":
try:
executor.max_workers = task_dict["max_workers"]
except NotImplementedError:
task_dict["future"].set_result(False)
else:
task_dict["future"].set_result(True)
elif ( # handle function submitted to the executor
task_dict is not None and "fn" in task_dict and "future" in task_dict
):
Expand Down
16 changes: 11 additions & 5 deletions executorlib/interactive/shared.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import contextlib
import importlib.util
import os
import queue
Expand DownExpand Up@@ -57,7 +58,7 @@ def execute_tasks(
task_dict = future_queue.get()
if "shutdown" in task_dict and task_dict["shutdown"]:
interface.shutdown(wait=task_dict["wait"])
future_queue.task_done()
_task_done(future_queue=future_queue)
if queue_join_on_shutdown:
future_queue.join()
break
Expand DownExpand Up@@ -117,10 +118,10 @@ def _execute_task_without_cache(
f.set_result(interface.send_and_receive_dict(input_dict=task_dict))
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
else:
future_queue.task_done()
_task_done(future_queue=future_queue)


def _execute_task_with_cache(
Expand DownExpand Up@@ -161,13 +162,18 @@ def _execute_task_with_cache(
f.set_result(result)
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
raise thread_exception
else:
future_queue.task_done()
_task_done(future_queue=future_queue)
else:
_, result = get_output(file_name=file_name)
future = task_dict["future"]
future.set_result(result)
_task_done(future_queue=future_queue)


def _task_done(future_queue: queue.Queue):
with contextlib.suppress(ValueError):
future_queue.task_done()
16 changes: 16 additions & 0 deletions tests/test_local_executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,6 +77,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with BlockAllocationExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with BlockAllocationExecutor(
max_workers=1,
Expand DownExpand Up@@ -107,6 +115,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with OneTaskPerProcessExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with OneTaskPerProcessExecutor(
max_cores=1,
Expand Down
80 changes: 80 additions & 0 deletions tests/test_local_executor_resize.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
import unittest
from executorlib import SingleNodeExecutor
from executorlib.standalone.serialize import cloudpickle_register


def sleep_funct(sec):
from time import sleep
sleep(sec)
return sec


class TestResizing(unittest.TestCase):
def test_without_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 1)
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_without_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_no_block_allocation(self):
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=False) as exe:
exe.max_workers = 2
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=True) as exe:
exe.max_workers = 2

def test_max_workers_stopped_executor(self):
exe = SingleNodeExecutor(block_allocation=True)
exe.shutdown(wait=True)
self.assertIsNone(exe.max_workers)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions executorlib/base/executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,14 @@ def __init__(self, max_cores: Optional[int] = None):
self._future_queue: Optional[queue.Queue] = queue.Queue()
self._process: Optional[Union[Thread, list[Thread]]] = None

@property
def max_workers(self) -> Optional[int]:
return self._process_kwargs.get("max_workers")

@max_workers.setter
def max_workers(self, max_workers: int):
raise NotImplementedError("The max_workers setter is not implemented.")

@property
def info(self) -> Optional[dict]:
"""
Expand Down
35 changes: 33 additions & 2 deletions executorlib/interactive/blockallocation.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import queue
from concurrent.futures import Future
from threading import Thread
from typing import Callable, Optional
Expand DownExpand Up@@ -27,7 +28,7 @@ class BlockAllocationExecutor(ExecutorBase):
Examples:

>>> import numpy as np
>>> from executorlib.interactive.shared import BlockAllocationExecutor
>>> from executorlib.interactive.blockallocation import BlockAllocationExecutor
>>>
>>> def calc(i, j, k):
>>> from mpi4py import MPI
Expand DownExpand Up@@ -58,16 +59,46 @@ def __init__(
executor_kwargs["spawner"] = spawner
executor_kwargs["queue_join_on_shutdown"] = False
self._process_kwargs = executor_kwargs
self._max_workers = max_workers
self._set_process(
process=[
Thread(
target=execute_tasks,
kwargs=executor_kwargs,
)
for _ in range(max_workers)
for _ in range(self._max_workers)
],
)

@property
def max_workers(self) -> int:
return self._max_workers

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue) and isinstance(
self._process, list
):
if self._max_workers > max_workers:
for _ in range(self._max_workers - max_workers):
self._future_queue.queue.insert(0, {"shutdown": True, "wait": True})
while len(self._process) > max_workers:
self._process = [
process for process in self._process if process.is_alive()
]
elif self._max_workers < max_workers:
new_process_lst = [
Thread(
target=execute_tasks,
kwargs=self._process_kwargs,
)
for _ in range(max_workers - self._max_workers)
]
for process_instance in new_process_lst:
process_instance.start()
self._process += new_process_lst
self._max_workers = max_workers

def submit( # type: ignore
self, fn: Callable, *args, resource_dict: Optional[dict] = None, **kwargs
) -> Future:
Expand Down
40 changes: 38 additions & 2 deletions executorlib/interactive/dependency.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,12 +76,39 @@ def info(self) -> Optional[dict]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "info", "future": f}
0, {"internal": True, "task": "get_info", "future": f}
)
return f.result()
else:
return None

@property
def max_workers(self) -> Optional[int]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "get_max_workers", "future": f}
)
return f.result()
else:
return None

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0,
{
"internal": True,
"task": "set_max_workers",
"max_workers": max_workers,
"future": f,
},
)
if not f.result():
raise NotImplementedError("The max_workers setter is not implemented.")

def submit( # type: ignore
self,
fn: Callable[..., Any],
Expand DownExpand Up@@ -188,8 +215,17 @@ def _execute_tasks_with_dependencies(
if ( # shutdown the executor
task_dict is not None and "internal" in task_dict and task_dict["internal"]
):
if task_dict["task"] == "info":
if task_dict["task"] == "get_info":
task_dict["future"].set_result(executor.info)
elif task_dict["task"] == "get_max_workers":
task_dict["future"].set_result(executor.max_workers)
elif task_dict["task"] == "set_max_workers":
try:
executor.max_workers = task_dict["max_workers"]
except NotImplementedError:
task_dict["future"].set_result(False)
else:
task_dict["future"].set_result(True)
elif ( # handle function submitted to the executor
task_dict is not None and "fn" in task_dict and "future" in task_dict
):
Expand Down
16 changes: 11 additions & 5 deletions executorlib/interactive/shared.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import contextlib
import importlib.util
import os
import queue
Expand DownExpand Up@@ -57,7 +58,7 @@ def execute_tasks(
task_dict = future_queue.get()
if "shutdown" in task_dict and task_dict["shutdown"]:
interface.shutdown(wait=task_dict["wait"])
future_queue.task_done()
_task_done(future_queue=future_queue)
if queue_join_on_shutdown:
future_queue.join()
break
Expand DownExpand Up@@ -117,10 +118,10 @@ def _execute_task_without_cache(
f.set_result(interface.send_and_receive_dict(input_dict=task_dict))
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
else:
future_queue.task_done()
_task_done(future_queue=future_queue)


def _execute_task_with_cache(
Expand DownExpand Up@@ -161,13 +162,18 @@ def _execute_task_with_cache(
f.set_result(result)
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
raise thread_exception
else:
future_queue.task_done()
_task_done(future_queue=future_queue)
else:
_, result = get_output(file_name=file_name)
future = task_dict["future"]
future.set_result(result)
_task_done(future_queue=future_queue)


def _task_done(future_queue: queue.Queue):
with contextlib.suppress(ValueError):
future_queue.task_done()
16 changes: 16 additions & 0 deletions tests/test_local_executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,6 +77,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with BlockAllocationExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with BlockAllocationExecutor(
max_workers=1,
Expand DownExpand Up@@ -107,6 +115,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with OneTaskPerProcessExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with OneTaskPerProcessExecutor(
max_cores=1,
Expand Down
80 changes: 80 additions & 0 deletions tests/test_local_executor_resize.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
import unittest
from executorlib import SingleNodeExecutor
from executorlib.standalone.serialize import cloudpickle_register


def sleep_funct(sec):
from time import sleep
sleep(sec)
return sec


class TestResizing(unittest.TestCase):
def test_without_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 1)
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_without_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_no_block_allocation(self):
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=False) as exe:
exe.max_workers = 2
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=True) as exe:
exe.max_workers = 2

def test_max_workers_stopped_executor(self):
exe = SingleNodeExecutor(block_allocation=True)
exe.shutdown(wait=True)
self.assertIsNone(exe.max_workers)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions executorlib/base/executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,14 @@ def __init__(self, max_cores: Optional[int] = None):
self._future_queue: Optional[queue.Queue] = queue.Queue()
self._process: Optional[Union[Thread, list[Thread]]] = None

@property
def max_workers(self) -> Optional[int]:
return self._process_kwargs.get("max_workers")

@max_workers.setter
def max_workers(self, max_workers: int):
raise NotImplementedError("The max_workers setter is not implemented.")

@property
def info(self) -> Optional[dict]:
"""
Expand Down
35 changes: 33 additions & 2 deletions executorlib/interactive/blockallocation.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import queue
from concurrent.futures import Future
from threading import Thread
from typing import Callable, Optional
Expand DownExpand Up@@ -27,7 +28,7 @@ class BlockAllocationExecutor(ExecutorBase):
Examples:

>>> import numpy as np
>>> from executorlib.interactive.shared import BlockAllocationExecutor
>>> from executorlib.interactive.blockallocation import BlockAllocationExecutor
>>>
>>> def calc(i, j, k):
>>> from mpi4py import MPI
Expand DownExpand Up@@ -58,16 +59,46 @@ def __init__(
executor_kwargs["spawner"] = spawner
executor_kwargs["queue_join_on_shutdown"] = False
self._process_kwargs = executor_kwargs
self._max_workers = max_workers
self._set_process(
process=[
Thread(
target=execute_tasks,
kwargs=executor_kwargs,
)
for _ in range(max_workers)
for _ in range(self._max_workers)
],
)

@property
def max_workers(self) -> int:
return self._max_workers

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue) and isinstance(
self._process, list
):
if self._max_workers > max_workers:
for _ in range(self._max_workers - max_workers):
self._future_queue.queue.insert(0, {"shutdown": True, "wait": True})
while len(self._process) > max_workers:
self._process = [
process for process in self._process if process.is_alive()
]
elif self._max_workers < max_workers:
new_process_lst = [
Thread(
target=execute_tasks,
kwargs=self._process_kwargs,
)
for _ in range(max_workers - self._max_workers)
]
for process_instance in new_process_lst:
process_instance.start()
self._process += new_process_lst
self._max_workers = max_workers

def submit( # type: ignore
self, fn: Callable, *args, resource_dict: Optional[dict] = None, **kwargs
) -> Future:
Expand Down
40 changes: 38 additions & 2 deletions executorlib/interactive/dependency.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,12 +76,39 @@ def info(self) -> Optional[dict]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "info", "future": f}
0, {"internal": True, "task": "get_info", "future": f}
)
return f.result()
else:
return None

@property
def max_workers(self) -> Optional[int]:
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0, {"internal": True, "task": "get_max_workers", "future": f}
)
return f.result()
else:
return None

@max_workers.setter
def max_workers(self, max_workers: int):
if isinstance(self._future_queue, queue.Queue):
f: Future = Future()
self._future_queue.queue.insert(
0,
{
"internal": True,
"task": "set_max_workers",
"max_workers": max_workers,
"future": f,
},
)
if not f.result():
raise NotImplementedError("The max_workers setter is not implemented.")

def submit( # type: ignore
self,
fn: Callable[..., Any],
Expand DownExpand Up@@ -188,8 +215,17 @@ def _execute_tasks_with_dependencies(
if ( # shutdown the executor
task_dict is not None and "internal" in task_dict and task_dict["internal"]
):
if task_dict["task"] == "info":
if task_dict["task"] == "get_info":
task_dict["future"].set_result(executor.info)
elif task_dict["task"] == "get_max_workers":
task_dict["future"].set_result(executor.max_workers)
elif task_dict["task"] == "set_max_workers":
try:
executor.max_workers = task_dict["max_workers"]
except NotImplementedError:
task_dict["future"].set_result(False)
else:
task_dict["future"].set_result(True)
elif ( # handle function submitted to the executor
task_dict is not None and "fn" in task_dict and "future" in task_dict
):
Expand Down
16 changes: 11 additions & 5 deletions executorlib/interactive/shared.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import contextlib
import importlib.util
import os
import queue
Expand DownExpand Up@@ -57,7 +58,7 @@ def execute_tasks(
task_dict = future_queue.get()
if "shutdown" in task_dict and task_dict["shutdown"]:
interface.shutdown(wait=task_dict["wait"])
future_queue.task_done()
_task_done(future_queue=future_queue)
if queue_join_on_shutdown:
future_queue.join()
break
Expand DownExpand Up@@ -117,10 +118,10 @@ def _execute_task_without_cache(
f.set_result(interface.send_and_receive_dict(input_dict=task_dict))
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
else:
future_queue.task_done()
_task_done(future_queue=future_queue)


def _execute_task_with_cache(
Expand DownExpand Up@@ -161,13 +162,18 @@ def _execute_task_with_cache(
f.set_result(result)
except Exception as thread_exception:
interface.shutdown(wait=True)
future_queue.task_done()
_task_done(future_queue=future_queue)
f.set_exception(exception=thread_exception)
raise thread_exception
else:
future_queue.task_done()
_task_done(future_queue=future_queue)
else:
_, result = get_output(file_name=file_name)
future = task_dict["future"]
future.set_result(result)
_task_done(future_queue=future_queue)


def _task_done(future_queue: queue.Queue):
with contextlib.suppress(ValueError):
future_queue.task_done()
16 changes: 16 additions & 0 deletions tests/test_local_executor.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,6 +77,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with BlockAllocationExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with BlockAllocationExecutor(
max_workers=1,
Expand DownExpand Up@@ -107,6 +115,14 @@ def test_pympiexecutor_two_workers(self):
self.assertTrue(fs_1.done())
self.assertTrue(fs_2.done())

def test_max_workers(self):
with OneTaskPerProcessExecutor(
max_workers=2,
executor_kwargs={},
spawner=MpiExecSpawner,
) as exe:
self.assertEqual(exe.max_workers, 2)

def test_pympiexecutor_one_worker(self):
with OneTaskPerProcessExecutor(
max_cores=1,
Expand Down
80 changes: 80 additions & 0 deletions tests/test_local_executor_resize.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
import unittest
from executorlib import SingleNodeExecutor
from executorlib.standalone.serialize import cloudpickle_register


def sleep_funct(sec):
from time import sleep
sleep(sec)
return sec


class TestResizing(unittest.TestCase):
def test_without_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 1)
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_without_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=True) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertTrue(len(exe) >= 1)
self.assertEqual(len(exe._process), 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_decrease(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=2, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
sleep_funct(sec=0.5)
exe.max_workers = 1
self.assertTrue(1 <= sum([f.done() for f in future_lst]) < 3)
self.assertEqual([f.result() for f in future_lst], [1, 1, 1, 1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_with_dependencies_increase(self):
cloudpickle_register(ind=1)
with SingleNodeExecutor(max_workers=1, block_allocation=True, disable_dependencies=False) as exe:
future_lst = [exe.submit(sleep_funct, 0.1) for _ in range(4)]
self.assertEqual([f.done() for f in future_lst], [False, False, False, False])
self.assertEqual(len(exe), 4)
self.assertEqual(exe.max_workers, 1)
future_lst[0].result()
exe.max_workers = 2
self.assertEqual(exe.max_workers, 2)
self.assertEqual([f.done() for f in future_lst], [True, False, False, False])
self.assertEqual([f.result() for f in future_lst], [0.1, 0.1, 0.1, 0.1])
self.assertEqual([f.done() for f in future_lst], [True, True, True, True])

def test_no_block_allocation(self):
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=False) as exe:
exe.max_workers = 2
with self.assertRaises(NotImplementedError):
with SingleNodeExecutor(block_allocation=False, disable_dependencies=True) as exe:
exe.max_workers = 2

def test_max_workers_stopped_executor(self):
exe = SingleNodeExecutor(block_allocation=True)
exe.shutdown(wait=True)
self.assertIsNone(exe.max_workers)