Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Add Mutex Python bindings
authorJean-Edouard BOULANGER <jean.edouard.boulanger@gmail.com>
Mon, 14 Mar 2022 17:30:23 +0000 (18:30 +0100)
committerJean-Edouard BOULANGER <jean.edouard.boulanger@gmail.com>
Tue, 15 Mar 2022 07:50:14 +0000 (08:50 +0100)
ChangeLog
docs/source/app_s4u.rst
examples/README.rst
examples/python/CMakeLists.txt
examples/python/synchro-mutex/synchro-mutex.py [new file with mode: 0644]
examples/python/synchro-mutex/synchro-mutex.tesh [new file with mode: 0644]
src/bindings/python/simgrid_python.cpp

index 4108344..76fb566 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -74,11 +74,12 @@ Python:
    - Mailbox: Mailbox.by_name()
  - Added the following bindings:
      - this_actor.warning()
-     - Mailbox.put_init() [example: examples/python/comm-waitallfor]
-     - Comm.detach() [example: examples/python/comm-waitallfor]
+     - Mailbox.put_init() [example: examples/python/comm-waitallfor/]
+     - Comm.detach() [example: examples/python/comm-waitallfor/]
      - Comm.wait_for() [example: examples/python/comm-waitfor/]
      - Comm.wait_any_for()
-     - Comm.wait_all_for() [example: examples/python/comm-waitallfor]
+     - Comm.wait_all_for() [example: examples/python/comm-waitallfor/]
+     - Mutex [example: examples/python/synchro-mutex/]
 
 Build System:
  - Remove target "make uninstall" which was incomplete and no longer maintained.
index eb19f07..7f9b1ae 100644 (file)
@@ -2446,7 +2446,15 @@ Synchronization Objects
 ⁣  Mutex
 ==============
 
-.. doxygenclass:: simgrid::s4u::Mutex
+.. tabs::
+
+   .. group-tab:: C++
+
+      .. doxygenclass:: simgrid::s4u::Mutex
+
+   .. group-tab:: Python
+
+      .. autoclass:: simgrid.Mutex
 
 Basic management
 ----------------
@@ -2463,6 +2471,13 @@ Basic management
 
          .. doxygenfunction:: simgrid::s4u::Mutex::create()
 
+      .. group-tab:: Python
+
+         .. code-block:: Python
+
+            from simgrid import Mutex
+            mutex = Mutex()
+
       .. group-tab:: C
 
          .. code-block:: C
@@ -2488,6 +2503,12 @@ Locking
          .. doxygenfunction:: simgrid::s4u::Mutex::try_lock()
          .. doxygenfunction:: simgrid::s4u::Mutex::unlock()
 
+      .. group-tab:: Python
+
+         .. automethod:: simgrid.Mutex.lock()
+         .. automethod:: simgrid.Mutex.try_lock()
+         .. automethod:: simgrid.Mutex.unlock()
+
       .. group-tab:: C
 
          .. doxygenfunction:: sg_mutex_lock(sg_mutex_t mutex)
index c11029c..35ac45f 100644 (file)
@@ -641,6 +641,8 @@ Shows how to use :cpp:type:`simgrid::s4u::Mutex` synchronization objects.
 
    .. example-tab:: examples/cpp/synchro-mutex/s4u-synchro-mutex.cpp
 
+   .. example-tab:: examples/python/synchro-mutex/synchro-mutex.py
+
 Semaphore
 ^^^^^^^^^
 
index aca123e..d5637d0 100644 (file)
@@ -3,7 +3,8 @@ foreach(example actor-create actor-daemon actor-join actor-kill actor-migrate ac
         comm-wait comm-waitall comm-waitallfor comm-waitany comm-waitfor
         exec-async exec-basic exec-dvfs exec-remote
         platform-profile platform-failures
-        network-nonlinear clusters-multicpu io-degradation exec-cpu-nonlinear)
+        network-nonlinear clusters-multicpu io-degradation exec-cpu-nonlinear
+        synchro-mutex)
   set(tesh_files    ${tesh_files}   ${CMAKE_CURRENT_SOURCE_DIR}/${example}/${example}.tesh)
   set(examples_src  ${examples_src} ${CMAKE_CURRENT_SOURCE_DIR}/${example}/${example}.py)
 
diff --git a/examples/python/synchro-mutex/synchro-mutex.py b/examples/python/synchro-mutex/synchro-mutex.py
new file mode 100644 (file)
index 0000000..5d68323
--- /dev/null
@@ -0,0 +1,123 @@
+from argparse import ArgumentParser
+from dataclasses import dataclass
+import sys
+
+from simgrid import Actor, Engine, Host, Mutex, this_actor
+
+
+def create_parser() -> ArgumentParser:
+    parser = ArgumentParser()
+    parser.add_argument(
+        '--platform',
+        type=str,
+        required=True,
+        help='path to the platform description'
+    )
+    parser.add_argument(
+        '--workers',
+        type=int,
+        default=6,
+        help='number of workers to start'
+    )
+    parser.add_argument(
+        '--trials-before-success',
+        type=int,
+        default=0,
+        help='number of attempts each workers need to make before getting the correct answer'
+             ' (i.e. number of simulated failures)'
+    )
+    return parser
+
+
+@dataclass
+class ResultHolder:
+    value: int
+
+
+class CalculationError(RuntimeError):
+    """ Fake calculation error
+    """
+    pass
+
+
+def worker_context_manager(mutex: Mutex, trials_before_success: int, result: ResultHolder):
+    """ Worker that uses a context manager to acquire/release the shared mutex
+    :param mutex: Shared mutex that guards read/write access to the shared result
+    :param trials_before_success: Number of simulated calculation failures before success
+    :param result: Shared result which will be updated by the worker
+    """
+    this_actor.info(f"I just started")
+    for trial in range(trials_before_success + 1):
+        try:
+            with mutex:
+                this_actor.info(f"acquired the mutex with context manager")
+                this_actor.sleep_for(1)
+                if trial < trials_before_success:
+                    raise CalculationError("did not manage to find the correct answer")
+                result.value += 1
+                this_actor.info(f"updated shared result, which is now {result.value}")
+        except CalculationError as e:
+            this_actor.warning(f"ran in trouble while calculating: {e}. Will retry shortly.")
+        finally:
+            this_actor.info(f"released the mutex after leaving the context manager")
+    this_actor.info("Bye now!")
+
+
+def worker(mutex: Mutex, trials_before_success: int, result: ResultHolder):
+    """ Worker that manually acquires/releases the shared mutex
+    :param mutex: Shared mutex that guards read/write access to the shared result
+    :param trials_before_success: Number of simulated calculation failures before success
+    :param result: Shared result which will be updated by the worker
+    """
+    this_actor.info(f"I just started")
+    for trial in range(trials_before_success + 1):
+        try:
+            mutex.lock()
+            this_actor.info(f"acquired the mutex manually")
+            this_actor.sleep_for(1)
+            if trial < trials_before_success:
+                raise CalculationError("did not manage to find the correct answer")
+            result.value += 1
+            this_actor.info(f"updated shared result, which is now {result.value}")
+        except CalculationError as e:
+            this_actor.warning(f"ran in trouble while calculating: {e}. Will retry shortly.")
+        finally:
+            this_actor.info(f"released the mutex manually")
+            mutex.unlock()
+    this_actor.info("Bye now!")
+
+
+def master(settings):
+    """ Spawns `--workers` workers and wait until they have all updated the shared result, then displays it before
+        leaving. Alternatively spawns `worker_context_manager()` and `worker()` workers.
+    :param settings: Simulation settings
+    """
+    result = ResultHolder(value=0)
+    mutex = Mutex()
+    actors = []
+    for i in range(settings.workers):
+        use_worker_context_manager = i % 2 == 0
+        actors.append(
+            Actor.create(
+                f"worker-{i}(mgr)" if use_worker_context_manager else f"worker-{i}",
+                Host.by_name("Jupiter" if use_worker_context_manager else "Tremblay"),
+                worker_context_manager if use_worker_context_manager else worker,
+                mutex,
+                settings.trials_before_success,
+                result
+            )
+        )
+    [actor.join() for actor in actors]
+    this_actor.info(f"The final result is: {result.value}")
+
+
+def main():
+    settings = create_parser().parse_known_args()[0]
+    e = Engine(sys.argv)
+    e.load_platform(settings.platform)
+    Actor.create("master", Host.by_name("Tremblay"), master, settings)
+    e.run()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/python/synchro-mutex/synchro-mutex.tesh b/examples/python/synchro-mutex/synchro-mutex.tesh
new file mode 100644 (file)
index 0000000..06c7c94
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/env tesh
+
+p Testing Mutex
+
+$ ${pythoncmd:=python3} ${PYTHON_TOOL_OPTIONS:=} ${bindir:=.}/synchro-mutex.py --platform ${platfdir}/two_hosts.xml --workers 0 "--log=root.fmt:[%10.6r]%e(%i:%a@%h)%e%m%n"
+>[  0.000000] (1:master@Tremblay) The final result is: 0
+
+$ ${pythoncmd:=python3} ${PYTHON_TOOL_OPTIONS:=} ${bindir:=.}/synchro-mutex.py --platform ${platfdir}/two_hosts.xml --workers 1 "--log=root.fmt:[%10.6r]%e(%i:%a@%h)%e%m%n"
+>[  0.000000] (2:worker-0(mgr)@Jupiter) I just started
+>[  0.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  1.000000] (2:worker-0(mgr)@Jupiter) updated shared result, which is now 1
+>[  1.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  1.000000] (2:worker-0(mgr)@Jupiter) Bye now!
+>[  1.000000] (1:master@Tremblay) The final result is: 1
+
+$ ${pythoncmd:=python3} ${PYTHON_TOOL_OPTIONS:=} ${bindir:=.}/synchro-mutex.py --platform ${platfdir}/two_hosts.xml --workers 1 --trials-before-success 5 "--log=root.fmt:[%10.6r]%e(%i:%a@%h)%e%m%n"
+>[  0.000000] (2:worker-0(mgr)@Jupiter) I just started
+>[  0.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  1.000000] (2:worker-0(mgr)@Jupiter) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  1.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  1.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  2.000000] (2:worker-0(mgr)@Jupiter) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  2.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  2.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  3.000000] (2:worker-0(mgr)@Jupiter) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  3.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  3.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  4.000000] (2:worker-0(mgr)@Jupiter) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  4.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  4.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  5.000000] (2:worker-0(mgr)@Jupiter) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  5.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  5.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  6.000000] (2:worker-0(mgr)@Jupiter) updated shared result, which is now 1
+>[  6.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  6.000000] (2:worker-0(mgr)@Jupiter) Bye now!
+>[  6.000000] (1:master@Tremblay) The final result is: 1
+
+$ ${pythoncmd:=python3} ${PYTHON_TOOL_OPTIONS:=} ${bindir:=.}/synchro-mutex.py --platform ${platfdir}/two_hosts.xml --workers 5 "--log=root.fmt:[%10.6r]%e(%i:%a@%h)%e%m%n"
+>[  0.000000] (2:worker-0(mgr)@Jupiter) I just started
+>[  0.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  0.000000] (3:worker-1@Tremblay) I just started
+>[  0.000000] (4:worker-2(mgr)@Jupiter) I just started
+>[  0.000000] (5:worker-3@Tremblay) I just started
+>[  0.000000] (6:worker-4(mgr)@Jupiter) I just started
+>[  1.000000] (2:worker-0(mgr)@Jupiter) updated shared result, which is now 1
+>[  1.000000] (3:worker-1@Tremblay) acquired the mutex manually
+>[  1.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  1.000000] (2:worker-0(mgr)@Jupiter) Bye now!
+>[  2.000000] (3:worker-1@Tremblay) updated shared result, which is now 2
+>[  2.000000] (3:worker-1@Tremblay) released the mutex manually
+>[  2.000000] (4:worker-2(mgr)@Jupiter) acquired the mutex with context manager
+>[  2.000000] (3:worker-1@Tremblay) Bye now!
+>[  3.000000] (4:worker-2(mgr)@Jupiter) updated shared result, which is now 3
+>[  3.000000] (5:worker-3@Tremblay) acquired the mutex manually
+>[  3.000000] (4:worker-2(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  3.000000] (4:worker-2(mgr)@Jupiter) Bye now!
+>[  4.000000] (5:worker-3@Tremblay) updated shared result, which is now 4
+>[  4.000000] (5:worker-3@Tremblay) released the mutex manually
+>[  4.000000] (6:worker-4(mgr)@Jupiter) acquired the mutex with context manager
+>[  4.000000] (5:worker-3@Tremblay) Bye now!
+>[  5.000000] (6:worker-4(mgr)@Jupiter) updated shared result, which is now 5
+>[  5.000000] (6:worker-4(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  5.000000] (6:worker-4(mgr)@Jupiter) Bye now!
+>[  5.000000] (1:master@Tremblay) The final result is: 5
+
+$ ${pythoncmd:=python3} ${PYTHON_TOOL_OPTIONS:=} ${bindir:=.}/synchro-mutex.py --platform ${platfdir}/two_hosts.xml --workers 3 --trials-before-success 2 "--log=root.fmt:[%10.6r]%e(%i:%a@%h)%e%m%n"
+>[  0.000000] (2:worker-0(mgr)@Jupiter) I just started
+>[  0.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  0.000000] (3:worker-1@Tremblay) I just started
+>[  0.000000] (4:worker-2(mgr)@Jupiter) I just started
+>[  1.000000] (3:worker-1@Tremblay) acquired the mutex manually
+>[  1.000000] (2:worker-0(mgr)@Jupiter) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  1.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  2.000000] (3:worker-1@Tremblay) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  2.000000] (3:worker-1@Tremblay) released the mutex manually
+>[  2.000000] (4:worker-2(mgr)@Jupiter) acquired the mutex with context manager
+>[  3.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  3.000000] (4:worker-2(mgr)@Jupiter) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  3.000000] (4:worker-2(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  4.000000] (3:worker-1@Tremblay) acquired the mutex manually
+>[  4.000000] (2:worker-0(mgr)@Jupiter) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  4.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  5.000000] (3:worker-1@Tremblay) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  5.000000] (3:worker-1@Tremblay) released the mutex manually
+>[  5.000000] (4:worker-2(mgr)@Jupiter) acquired the mutex with context manager
+>[  6.000000] (2:worker-0(mgr)@Jupiter) acquired the mutex with context manager
+>[  6.000000] (4:worker-2(mgr)@Jupiter) ran in trouble while calculating: did not manage to find the correct answer. Will retry shortly.
+>[  6.000000] (4:worker-2(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  7.000000] (2:worker-0(mgr)@Jupiter) updated shared result, which is now 1
+>[  7.000000] (3:worker-1@Tremblay) acquired the mutex manually
+>[  7.000000] (2:worker-0(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  7.000000] (2:worker-0(mgr)@Jupiter) Bye now!
+>[  8.000000] (3:worker-1@Tremblay) updated shared result, which is now 2
+>[  8.000000] (3:worker-1@Tremblay) released the mutex manually
+>[  8.000000] (4:worker-2(mgr)@Jupiter) acquired the mutex with context manager
+>[  8.000000] (3:worker-1@Tremblay) Bye now!
+>[  9.000000] (4:worker-2(mgr)@Jupiter) updated shared result, which is now 3
+>[  9.000000] (4:worker-2(mgr)@Jupiter) released the mutex after leaving the context manager
+>[  9.000000] (4:worker-2(mgr)@Jupiter) Bye now!
+>[  9.000000] (1:master@Tremblay) The final result is: 3
index 0b1721b..3603e0d 100644 (file)
@@ -33,6 +33,7 @@
 #include <simgrid/s4u/Host.hpp>
 #include <simgrid/s4u/Link.hpp>
 #include <simgrid/s4u/Mailbox.hpp>
+#include <simgrid/s4u/Mutex.hpp>
 #include <simgrid/s4u/NetZone.hpp>
 #include <simgrid/version.h>
 
@@ -48,6 +49,8 @@ using simgrid::s4u::Engine;
 using simgrid::s4u::Host;
 using simgrid::s4u::Link;
 using simgrid::s4u::Mailbox;
+using simgrid::s4u::Mutex;
+using simgrid::s4u::MutexPtr;
 
 XBT_LOG_NEW_DEFAULT_CATEGORY(python, "python");
 
@@ -774,6 +777,20 @@ PYBIND11_MODULE(simgrid, m)
       .def("wait", &simgrid::s4u::Exec::wait, py::call_guard<py::gil_scoped_release>(),
            "Block until the completion of that execution.");
 
+  /* Class Mutex */
+  py::class_<Mutex, MutexPtr>(m, "Mutex",
+                              "A classical mutex, but blocking in the simulation world."
+                              "See the C++ documentation for details.")
+      .def(py::init<>(&Mutex::create))
+      .def("lock", &Mutex::lock, py::call_guard<py::gil_scoped_release>(), "Block until the mutex is acquired.")
+      .def("try_lock", &Mutex::try_lock, py::call_guard<py::gil_scoped_release>(),
+           "Try to acquire the mutex. Return true if the mutex was acquired, false otherwise.")
+      .def("unlock", &Mutex::unlock, py::call_guard<py::gil_scoped_release>(), "Release the mutex")
+      // Allow mutexes to be automatically acquired/released with a context manager: `with mutex: ...`
+      .def("__enter__", [](Mutex* self){ self->lock(); }, py::call_guard<py::gil_scoped_release>())
+      .def("__exit__", [](Mutex* self, py::object&, py::object&, py::object&){ self->unlock(); },
+          py::call_guard<py::gil_scoped_release>());
+
   /* Class Actor */
   py::class_<simgrid::s4u::Actor, ActorPtr>(m, "Actor",
                                             "An actor is an independent stream of execution in your distributed "
@@ -824,7 +841,7 @@ PYBIND11_MODULE(simgrid, m)
            "Returns True if that actor is a daemon and will be terminated automatically when the last non-daemon actor "
            "terminates.")
       .def("join", py::overload_cast<double>(&Actor::join, py::const_), py::call_guard<py::gil_scoped_release>(),
-           "Wait for the actor to finish (more info in the C++ documentation).", py::arg("timeout"))
+           "Wait for the actor to finish (more info in the C++ documentation).", py::arg("timeout") = -1)
       .def("kill", &Actor::kill, py::call_guard<py::gil_scoped_release>(), "Kill that actor")
       .def("self", &Actor::self, "Retrieves the current actor.")
       .def("is_suspended", &Actor::is_suspended, "Returns True if that actor is currently suspended.")