From 8df517e2e3344decdc3808e03dab02e3d1c10864 Mon Sep 17 00:00:00 2001 From: Gavin Parpart Date: Fri, 3 Feb 2023 16:50:52 -0800 Subject: [PATCH 1/7] Update runtime and process to be "with" context managers. Add tests to verify this. --- src/lava/magma/core/process/process.py | 10 ++ src/lava/magma/runtime/runtime.py | 8 ++ .../magma/runtime/test_context_manager.py | 114 ++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 tests/lava/magma/runtime/test_context_manager.py diff --git a/src/lava/magma/core/process/process.py b/src/lava/magma/core/process/process.py index 471980c29..c9961cd0d 100644 --- a/src/lava/magma/core/process/process.py +++ b/src/lava/magma/core/process/process.py @@ -221,6 +221,16 @@ def __del__(self): """ self.stop() + def __enter__(self): + """Required for "with" block.""" + pass + + def __exit__(self, exc_type, exc_val, exc_tb): + """ + Stop the runtime when exiting "with" block. + """ + self.stop() + def _post_init(self): """Called after __init__() method of any sub class via ProcessMetaClass to finalize initialization leading to following diff --git a/src/lava/magma/runtime/runtime.py b/src/lava/magma/runtime/runtime.py index 32abfed43..3d554f024 100644 --- a/src/lava/magma/runtime/runtime.py +++ b/src/lava/magma/runtime/runtime.py @@ -135,6 +135,14 @@ def __del__(self): if self._is_started: self.stop() + def __enter__(self): + """Initialize the runtime on entering a "with" block""" + self.initialize() + + def __exit__(self, exc_type, exc_val, exc_tb): + """Stop the runtime when exiting "with" block.""" + self.stop() + def initialize(self, node_cfg_idx: int = 0): """Initializes the runtime""" self._build_message_infrastructure() diff --git a/tests/lava/magma/runtime/test_context_manager.py b/tests/lava/magma/runtime/test_context_manager.py new file mode 100644 index 000000000..fb48c90d6 --- /dev/null +++ b/tests/lava/magma/runtime/test_context_manager.py @@ -0,0 +1,114 @@ +import unittest +from time import sleep + +from lava.magma.compiler.compiler import Compiler +from lava.magma.core.decorator import implements, requires +from lava.magma.core.model.py.model import PyLoihiProcessModel +from lava.magma.core.model.py.type import LavaPyType +from lava.magma.core.process.message_interface_enum import ActorType +from lava.magma.core.process.process import AbstractProcess +from lava.magma.core.process.variable import Var +from lava.magma.core.resources import CPU +from lava.magma.core.run_conditions import RunContinuous, RunSteps +from lava.magma.core.run_configs import RunConfig +from lava.magma.core.sync.domain import SyncDomain +from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol +from lava.magma.runtime.runtime import Runtime + + +class SimpleProcess(AbstractProcess): + def __init__(self, **kwargs): + super().__init__() + shape = kwargs["shape"] + self.u = Var(shape=shape, init=0) + self.v = Var(shape=shape, init=0) + + +@implements(proc=SimpleProcess, protocol=LoihiProtocol) +@requires(CPU) +class SimpleProcessModel(PyLoihiProcessModel): + """ + Defines a SimpleProcessModel + """ + u = LavaPyType(int, int) + v = LavaPyType(int, int) + + +class SimpleRunConfig(RunConfig): + """ + Defines a simple run config + """ + + def __init__(self, **kwargs): + sync_domains = kwargs.pop("sync_domains") + super().__init__(custom_sync_domains=sync_domains) + self.model = None + if "model" in kwargs: + self.model = kwargs.pop("model") + + def select(self, process, proc_models): + if self.model is not None: + if self.model == "sub" and isinstance(process, SimpleProcess): + return proc_models[1] + + return proc_models[0] + + +class TestContextManager(unittest.TestCase): + def tearDown(self) -> None: + """ + Ensures process/runtime is stopped if context manager fails to + """ + self.stoppable.stop() + + def test_context_manager_stops_process(self): + """ + Verifies context manager stops process when exiting "with" block + """ + process = SimpleProcess(shape=(2, 2)) + self.stoppable = process + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + + with process: + process.run(condition=RunContinuous(), run_cfg=run_config) + self.assertTrue(process.runtime._is_running) + self.assertTrue(process.runtime._is_started) + sleep(2) + + self.assertFalse(process.runtime._is_running) + self.assertFalse(process.runtime._is_started) + + def test_context_manager_stops_runtime(self): + """ + Verifies context manager stops runtime when exiting "with" block + """ + self.process = SimpleProcess(shape=(2, 2)) + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), + [self.process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + compiler = Compiler() + executable = compiler.compile(self.process, run_config) + runtime = Runtime(executable, + ActorType.MultiProcessing) + executable.assign_runtime_to_all_processes(runtime) + + self.stoppable = runtime + + with runtime: + self.assertTrue(runtime._is_initialized) + self.assertFalse(runtime._is_running) + self.assertFalse(runtime._is_started) + + runtime.start(run_condition=RunContinuous()) + + self.assertTrue(runtime._is_running) + self.assertTrue(runtime._is_started) + sleep(2) + + self.assertFalse(runtime._is_running) + self.assertFalse(runtime._is_started) + + +if __name__ == '__main__': + unittest.main() From 26e7d3d5828773e4928bee9308c14e6608b2c5ae Mon Sep 17 00:00:00 2001 From: Gavin Parpart Date: Tue, 14 Feb 2023 21:15:27 -0800 Subject: [PATCH 2/7] Update docstrings to address feedback --- src/lava/magma/core/process/process.py | 4 +--- src/lava/magma/runtime/runtime.py | 5 ++-- .../magma/runtime/test_context_manager.py | 23 ++++++++----------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/lava/magma/core/process/process.py b/src/lava/magma/core/process/process.py index c9961cd0d..c9688fb63 100644 --- a/src/lava/magma/core/process/process.py +++ b/src/lava/magma/core/process/process.py @@ -226,9 +226,7 @@ def __enter__(self): pass def __exit__(self, exc_type, exc_val, exc_tb): - """ - Stop the runtime when exiting "with" block. - """ + """Stop the runtime when exiting "with" block of a context manager.""" self.stop() def _post_init(self): diff --git a/src/lava/magma/runtime/runtime.py b/src/lava/magma/runtime/runtime.py index 3d554f024..bbf19a631 100644 --- a/src/lava/magma/runtime/runtime.py +++ b/src/lava/magma/runtime/runtime.py @@ -136,11 +136,12 @@ def __del__(self): self.stop() def __enter__(self): - """Initialize the runtime on entering a "with" block""" + """Initialize the runtime on entering "with" block of a context manager. + """ self.initialize() def __exit__(self, exc_type, exc_val, exc_tb): - """Stop the runtime when exiting "with" block.""" + """Stop the runtime when exiting "with" block of a context manager.""" self.stop() def initialize(self, node_cfg_idx: int = 0): diff --git a/tests/lava/magma/runtime/test_context_manager.py b/tests/lava/magma/runtime/test_context_manager.py index fb48c90d6..6aadc0f29 100644 --- a/tests/lava/magma/runtime/test_context_manager.py +++ b/tests/lava/magma/runtime/test_context_manager.py @@ -1,3 +1,7 @@ +# Copyright (C) 2021-22 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + import unittest from time import sleep @@ -27,18 +31,11 @@ def __init__(self, **kwargs): @implements(proc=SimpleProcess, protocol=LoihiProtocol) @requires(CPU) class SimpleProcessModel(PyLoihiProcessModel): - """ - Defines a SimpleProcessModel - """ u = LavaPyType(int, int) v = LavaPyType(int, int) class SimpleRunConfig(RunConfig): - """ - Defines a simple run config - """ - def __init__(self, **kwargs): sync_domains = kwargs.pop("sync_domains") super().__init__(custom_sync_domains=sync_domains) @@ -57,13 +54,13 @@ def select(self, process, proc_models): class TestContextManager(unittest.TestCase): def tearDown(self) -> None: """ - Ensures process/runtime is stopped if context manager fails to + Ensures process/runtime is stopped if context manager fails to. """ self.stoppable.stop() def test_context_manager_stops_process(self): """ - Verifies context manager stops process when exiting "with" block + Verifies context manager stops process when exiting "with" block. """ process = SimpleProcess(shape=(2, 2)) self.stoppable = process @@ -81,14 +78,14 @@ def test_context_manager_stops_process(self): def test_context_manager_stops_runtime(self): """ - Verifies context manager stops runtime when exiting "with" block + Verifies context manager stops runtime when exiting "with" block. """ - self.process = SimpleProcess(shape=(2, 2)) + process = SimpleProcess(shape=(2, 2)) simple_sync_domain = SyncDomain("simple", LoihiProtocol(), - [self.process]) + [process]) run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) compiler = Compiler() - executable = compiler.compile(self.process, run_config) + executable = compiler.compile(process, run_config) runtime = Runtime(executable, ActorType.MultiProcessing) executable.assign_runtime_to_all_processes(runtime) From 2af0ee7f5d04ae4f355e4e54dd3a781126d67470 Mon Sep 17 00:00:00 2001 From: Gavin Parpart Date: Tue, 14 Feb 2023 21:20:52 -0800 Subject: [PATCH 3/7] Make process inherit __enter__ from AbstractContextManager rather than redefining it. --- src/lava/magma/core/process/process.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lava/magma/core/process/process.py b/src/lava/magma/core/process/process.py index c9688fb63..1928b1ca0 100644 --- a/src/lava/magma/core/process/process.py +++ b/src/lava/magma/core/process/process.py @@ -7,6 +7,7 @@ import typing as ty from _collections import OrderedDict from dataclasses import dataclass +from contextlib import AbstractContextManager from lava.magma.compiler.executable import Executable from lava.magma.core.process.interfaces import \ @@ -35,7 +36,7 @@ def __call__(cls, *args, **kwargs): return obj -class AbstractProcess(metaclass=ProcessPostInitCaller): +class AbstractProcess(AbstractContextManager, metaclass=ProcessPostInitCaller): """The notion of a Process is inspired by the Communicating Sequential Process paradigm for distributed, parallel, and asynchronous programming. @@ -221,10 +222,6 @@ def __del__(self): """ self.stop() - def __enter__(self): - """Required for "with" block.""" - pass - def __exit__(self, exc_type, exc_val, exc_tb): """Stop the runtime when exiting "with" block of a context manager.""" self.stop() From 6de47f0435b86cd017077ddb28799ea76f7de3f9 Mon Sep 17 00:00:00 2001 From: Gavin Parpart Date: Tue, 14 Feb 2023 21:33:50 -0800 Subject: [PATCH 4/7] Add Stoppable abstract class for typing. --- tests/lava/magma/runtime/test_context_manager.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/lava/magma/runtime/test_context_manager.py b/tests/lava/magma/runtime/test_context_manager.py index 6aadc0f29..7f029d6e1 100644 --- a/tests/lava/magma/runtime/test_context_manager.py +++ b/tests/lava/magma/runtime/test_context_manager.py @@ -1,8 +1,9 @@ # Copyright (C) 2021-22 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ - +import abc import unittest +from typing import Optional from time import sleep from lava.magma.compiler.compiler import Compiler @@ -51,12 +52,22 @@ def select(self, process, proc_models): return proc_models[0] +class Stoppable(abc.ABC): + @abc.abstractmethod + def stop(self) -> None: + ... + + class TestContextManager(unittest.TestCase): + def setUp(self) -> None: + self.stoppable: Optional[Stoppable] = None + def tearDown(self) -> None: """ Ensures process/runtime is stopped if context manager fails to. """ - self.stoppable.stop() + if self.stoppable is not None: + self.stoppable.stop() def test_context_manager_stops_process(self): """ From da5e3441283c68be9a694bd22adff79a65ac3703 Mon Sep 17 00:00:00 2001 From: Gavin Parpart Date: Tue, 14 Feb 2023 22:03:14 -0800 Subject: [PATCH 5/7] Revert "Make process inherit __enter__ from AbstractContextManager rather than redefining it." This reverts commit 2af0ee7f5d04ae4f355e4e54dd3a781126d67470. --- src/lava/magma/core/process/process.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lava/magma/core/process/process.py b/src/lava/magma/core/process/process.py index 1928b1ca0..c9688fb63 100644 --- a/src/lava/magma/core/process/process.py +++ b/src/lava/magma/core/process/process.py @@ -7,7 +7,6 @@ import typing as ty from _collections import OrderedDict from dataclasses import dataclass -from contextlib import AbstractContextManager from lava.magma.compiler.executable import Executable from lava.magma.core.process.interfaces import \ @@ -36,7 +35,7 @@ def __call__(cls, *args, **kwargs): return obj -class AbstractProcess(AbstractContextManager, metaclass=ProcessPostInitCaller): +class AbstractProcess(metaclass=ProcessPostInitCaller): """The notion of a Process is inspired by the Communicating Sequential Process paradigm for distributed, parallel, and asynchronous programming. @@ -222,6 +221,10 @@ def __del__(self): """ self.stop() + def __enter__(self): + """Required for "with" block.""" + pass + def __exit__(self, exc_type, exc_val, exc_tb): """Stop the runtime when exiting "with" block of a context manager.""" self.stop() From b90179fd09ea8d7169916d695122399f4502aaa5 Mon Sep 17 00:00:00 2001 From: Gavin Parpart Date: Tue, 14 Feb 2023 22:04:59 -0800 Subject: [PATCH 6/7] Update enter docstring --- src/lava/magma/core/process/process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lava/magma/core/process/process.py b/src/lava/magma/core/process/process.py index c9688fb63..ca4cbf33f 100644 --- a/src/lava/magma/core/process/process.py +++ b/src/lava/magma/core/process/process.py @@ -222,7 +222,7 @@ def __del__(self): self.stop() def __enter__(self): - """Required for "with" block.""" + """Executed when Process enters a "with" block of a context manager.""" pass def __exit__(self, exc_type, exc_val, exc_tb): From 00ebf1917c175f0a34989a8681ba72c74e85483b Mon Sep 17 00:00:00 2001 From: Gavin Parpart Date: Tue, 14 Feb 2023 22:08:48 -0800 Subject: [PATCH 7/7] Add tests for processes started with RunSteps --- .../magma/runtime/test_context_manager.py | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/lava/magma/runtime/test_context_manager.py b/tests/lava/magma/runtime/test_context_manager.py index 7f029d6e1..f792fb8f2 100644 --- a/tests/lava/magma/runtime/test_context_manager.py +++ b/tests/lava/magma/runtime/test_context_manager.py @@ -69,9 +69,10 @@ def tearDown(self) -> None: if self.stoppable is not None: self.stoppable.stop() - def test_context_manager_stops_process(self): + def test_context_manager_stops_process_continuous(self): """ Verifies context manager stops process when exiting "with" block. + Process is started with RunContinuous. """ process = SimpleProcess(shape=(2, 2)) self.stoppable = process @@ -87,6 +88,45 @@ def test_context_manager_stops_process(self): self.assertFalse(process.runtime._is_running) self.assertFalse(process.runtime._is_started) + def test_context_manager_stops_process_runsteps_nonblocking(self): + """ + Verifies context manager stops process when exiting "with" block. + Process is started with RunSteps(blocking=false). + """ + process = SimpleProcess(shape=(2, 2)) + self.stoppable = process + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), + [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + + with process: + process.run(condition=RunSteps(200, blocking=False), + run_cfg=run_config) + self.assertTrue(process.runtime._is_running) + self.assertTrue(process.runtime._is_started) + + self.assertFalse(process.runtime._is_running) + self.assertFalse(process.runtime._is_started) + + def test_context_manager_stops_process_runsteps_blocking(self): + """ + Verifies context manager stops process when exiting "with" block. + Process is started with RunSteps(blocking=true). + """ + process = SimpleProcess(shape=(2, 2)) + self.stoppable = process + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), + [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + + with process: + process.run(condition=RunSteps(200, blocking=True), + run_cfg=run_config) + sleep(2) + + self.assertFalse(process.runtime._is_running) + self.assertFalse(process.runtime._is_started) + def test_context_manager_stops_runtime(self): """ Verifies context manager stops runtime when exiting "with" block.