Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Add Semaphore Python bindings
authorJean-Edouard BOULANGER <jean.edouard.boulanger@gmail.com>
Thu, 17 Mar 2022 18:56:48 +0000 (19:56 +0100)
committerJean-Edouard BOULANGER <jean.edouard.boulanger@gmail.com>
Thu, 17 Mar 2022 18:56:48 +0000 (19:56 +0100)
ChangeLog
MANIFEST.in
docs/source/app_s4u.rst
examples/python/CMakeLists.txt
examples/python/synchro-semaphore/synchro-semaphore.py [new file with mode: 0644]
examples/python/synchro-semaphore/synchro-semaphore.tesh [new file with mode: 0644]
src/bindings/python/simgrid_python.cpp

index bdeb7b5..178bbbf 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -52,7 +52,7 @@ Models:
      communications between same source and destination inside the ptask).
    - Parameters:
      - "--cfg=host/model:ptask_BMF": enable the model.
-     - "--cfg=bmf/max-iterations: <N>": maximum number of iterations performed 
+     - "--cfg=bmf/max-iterations: <N>": maximum number of iterations performed
         by BMF solver (default: 1000).
      - "--cfg=bmf/selective-update:<true/false>": enable/disable the
       selective-update optimization. Only invalidates and recomputes modified
@@ -61,9 +61,9 @@ Models:
    - This model requires Eigen3 library. Make sure Eigen3 is installed to use BMF.
 
 General:
-  - Modifications of the Profile mechanism, with some impact on users 
+  - Modifications of the Profile mechanism, with some impact on users
     - Addition of a new (S4U) method to init profiles from generic functions to improve versatility
-    - Fix initial behaviour of state_profiles 
+    - Fix initial behaviour of state_profiles
     - Modify periodicity to behave like a period, and not like a loop delay
 
 XBT:
@@ -83,6 +83,7 @@ Python:
      - Comm.wait_all_for() [example: examples/python/comm-waitallfor/]
      - Mutex [example: examples/python/synchro-mutex/]
      - Barrier [example: examples/python/synchro-barrier/]
+     - Semaphore [example: examples/python/synchro-semaphore/]
 
 Build System:
  - Remove target "make uninstall" which was incomplete and no longer maintained.
index 6fad2cb..34a5788 100644 (file)
@@ -553,6 +553,8 @@ include examples/python/synchro-barrier/synchro-barrier.py
 include examples/python/synchro-barrier/synchro-barrier.tesh
 include examples/python/synchro-mutex/synchro-mutex.py
 include examples/python/synchro-mutex/synchro-mutex.tesh
+include examples/python/synchro-semaphore/synchro-semaphore.py
+include examples/python/synchro-semaphore/synchro-semaphore.tesh
 include examples/smpi/NAS/DGraph.c
 include examples/smpi/NAS/DGraph.h
 include examples/smpi/NAS/README.install
index 22f85cb..2047854 100644 (file)
@@ -2485,6 +2485,8 @@ Basic management
                 # Access shared resource ...
                 pass
 
+         .. automethod:: simgrid.Mutex.__init__
+
       .. group-tab:: C
 
          .. code-block:: C
@@ -2512,9 +2514,9 @@ Locking
 
       .. group-tab:: Python
 
-         .. automethod:: simgrid.Mutex.lock()
-         .. automethod:: simgrid.Mutex.try_lock()
-         .. automethod:: simgrid.Mutex.unlock()
+         .. automethod:: simgrid.Mutex.lock
+         .. automethod:: simgrid.Mutex.try_lock
+         .. automethod:: simgrid.Mutex.unlock
 
       .. group-tab:: C
 
@@ -2558,7 +2560,8 @@ Locking
          from simgrid import Barrier
          barrier = Barrier(2)
 
-      .. automethod:: simgrid.Barrier.wait()
+      .. automethod:: simgrid.Barrier.__init__
+      .. automethod:: simgrid.Barrier.wait
 
    .. group-tab:: C
 
@@ -2641,8 +2644,15 @@ Waiting and notifying
 ⁣  Semaphore
 ==================
 
-.. doxygenclass:: simgrid::s4u::Semaphore
+.. tabs::
 
+   .. group-tab:: C++
+
+      .. doxygenclass:: simgrid::s4u::Semaphore
+
+   .. group-tab:: Python
+
+      .. autoclass:: simgrid.Semaphore
 
 Basic management
 ----------------
@@ -2658,6 +2668,19 @@ Basic management
          .. doxygentypedef:: SemaphorePtr
          .. doxygenfunction:: simgrid::s4u::Semaphore::create(unsigned int initial_capacity)
 
+      .. group-tab:: Python
+
+         .. code-block:: Python
+
+            from simgrid import Semaphore
+            semaphore = Semaphore(1)
+            # Automatically acquire the semaphore, and release it after leaving the scope.
+            with semaphore:
+                # Do something with the shared resource
+                pass
+
+         .. automethod:: simgrid.Semaphore.__init__
+
       .. group-tab:: C
 
          .. code-block:: C
@@ -2685,6 +2708,14 @@ Locking
          .. doxygenfunction:: simgrid::s4u::Semaphore::release()
          .. doxygenfunction:: simgrid::s4u::Semaphore::would_block() const
 
+      .. group-tab:: Python
+
+         .. automethod:: simgrid.Semaphore.acquire
+         .. automethod:: simgrid.Semaphore.acquire_timeout
+         .. autoattribute:: simgrid.Semaphore.capacity
+         .. automethod:: simgrid.Semaphore.release
+         .. autoattribute:: simgrid.Semaphore.would_block
+
       .. group-tab:: C
 
          .. doxygenfunction:: sg_sem_acquire(sg_sem_t sem)
index 0803033..9f04d7a 100644 (file)
@@ -4,7 +4,7 @@ foreach(example actor-create actor-daemon actor-join actor-kill actor-migrate ac
         exec-async exec-basic exec-dvfs exec-remote
         platform-profile platform-failures
         network-nonlinear clusters-multicpu io-degradation exec-cpu-nonlinear
-        synchro-barrier synchro-mutex)
+        synchro-barrier synchro-mutex synchro-semaphore)
   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-semaphore/synchro-semaphore.py b/examples/python/synchro-semaphore/synchro-semaphore.py
new file mode 100644 (file)
index 0000000..58e5b16
--- /dev/null
@@ -0,0 +1,76 @@
+# Copyright (c) 2010-2022. The SimGrid Team. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the license (GNU LGPL) which comes with this package.
+
+from typing import List, Optional
+from dataclasses import dataclass
+from argparse import ArgumentParser
+import sys
+
+from simgrid import Actor, Engine, Host, Semaphore, this_actor
+
+
+@dataclass
+class Shared:
+    buffer: str
+
+
+END_MARKER = ""
+
+
+shared = Shared("")
+sem_empty = Semaphore(1)  # indicates whether the buffer is empty
+sem_full = Semaphore(0)  # indicates whether the buffer is full
+
+
+def create_parser():
+    parser = ArgumentParser()
+    parser.add_argument(
+        '--platform',
+        type=str,
+        required=True,
+        help='path to the platform description'
+    )
+    parser.add_argument(
+        '--words',
+        type=lambda raw_words: raw_words.split(","),
+        default=["one", "two", "three"],
+        help='Comma-delimited list of words sent by the producer to the consumer'
+    )
+    return parser
+
+
+def producer(words: List[str]):
+    this_actor.info("starting consuming")
+    for word in words + [END_MARKER]:
+        sem_empty.acquire()
+        this_actor.sleep_for(1)
+        this_actor.info(f"Pushing '{word}'")
+        shared.buffer = word
+        sem_full.release()
+    this_actor.info("Bye!")
+
+
+def consumer():
+    this_actor.info("starting producing")
+    word: Optional[str] = None
+    while word != END_MARKER:
+        sem_full.acquire()
+        word = str(shared.buffer)
+        sem_empty.release()
+        this_actor.info(f"Receiving '{word}'")
+    this_actor.info("Bye!")
+
+
+def main():
+    settings = create_parser().parse_known_args()[0]
+    e = Engine(sys.argv)
+    e.load_platform(settings.platform)
+    Actor.create("producer", Host.by_name("Tremblay"), producer, settings.words)
+    Actor.create("consumer", Host.by_name("Jupiter"), consumer)
+    e.run()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/python/synchro-semaphore/synchro-semaphore.tesh b/examples/python/synchro-semaphore/synchro-semaphore.tesh
new file mode 100644 (file)
index 0000000..5a05a04
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/env tesh
+
+p Testing Semaphore
+
+$ ${pythoncmd:=python3} ${PYTHON_TOOL_OPTIONS:=} ${bindir:=.}/synchro-semaphore.py --platform ${platfdir}/two_hosts.xml --words hello "--log=root.fmt:[%10.6r]%e(%i:%a@%h)%e%m%n"
+>[  0.000000] (1:producer@Tremblay) starting consuming
+>[  0.000000] (2:consumer@Jupiter) starting producing
+>[  1.000000] (1:producer@Tremblay) Pushing 'hello'
+>[  1.000000] (2:consumer@Jupiter) Receiving 'hello'
+>[  2.000000] (1:producer@Tremblay) Pushing ''
+>[  2.000000] (1:producer@Tremblay) Bye!
+>[  2.000000] (2:consumer@Jupiter) Receiving ''
+>[  2.000000] (2:consumer@Jupiter) Bye!
+
+$ ${pythoncmd:=python3} ${PYTHON_TOOL_OPTIONS:=} ${bindir:=.}/synchro-semaphore.py --platform ${platfdir}/two_hosts.xml --words one,two,three "--log=root.fmt:[%10.6r]%e(%i:%a@%h)%e%m%n"
+>[  0.000000] (1:producer@Tremblay) starting consuming
+>[  0.000000] (2:consumer@Jupiter) starting producing
+>[  1.000000] (1:producer@Tremblay) Pushing 'one'
+>[  1.000000] (2:consumer@Jupiter) Receiving 'one'
+>[  2.000000] (1:producer@Tremblay) Pushing 'two'
+>[  2.000000] (2:consumer@Jupiter) Receiving 'two'
+>[  3.000000] (1:producer@Tremblay) Pushing 'three'
+>[  3.000000] (2:consumer@Jupiter) Receiving 'three'
+>[  4.000000] (1:producer@Tremblay) Pushing ''
+>[  4.000000] (1:producer@Tremblay) Bye!
+>[  4.000000] (2:consumer@Jupiter) Receiving ''
+>[  4.000000] (2:consumer@Jupiter) Bye!
index eded4f1..d08e791 100644 (file)
@@ -36,6 +36,7 @@
 #include <simgrid/s4u/Mailbox.hpp>
 #include <simgrid/s4u/Mutex.hpp>
 #include <simgrid/s4u/NetZone.hpp>
+#include <simgrid/s4u/Semaphore.hpp>
 #include <simgrid/version.h>
 
 #include <algorithm>
@@ -54,6 +55,8 @@ using simgrid::s4u::Link;
 using simgrid::s4u::Mailbox;
 using simgrid::s4u::Mutex;
 using simgrid::s4u::MutexPtr;
+using simgrid::s4u::Semaphore;
+using simgrid::s4u::SemaphorePtr;
 
 XBT_LOG_NEW_DEFAULT_CATEGORY(python, "python");
 
@@ -780,15 +783,37 @@ 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 Semaphore */
+  py::class_<Semaphore, SemaphorePtr>(m, "Semaphore",
+                                      "A classical semaphore, but blocking in the simulation world. See the C++ "
+                                      "documentation for details.")
+      .def(py::init<>(&Semaphore::create), py::call_guard<py::gil_scoped_release>(), py::arg("capacity"),
+           "Semaphore constructor.")
+      .def("acquire", &Semaphore::acquire, py::call_guard<py::gil_scoped_release>(),
+           "Acquire on the semaphore object with no timeout. Blocks until the semaphore is acquired.")
+      .def("acquire_timeout", &Semaphore::acquire_timeout, py::call_guard<py::gil_scoped_release>(), py::arg("timeout"),
+           "Acquire on the semaphore object with no timeout. Blocks until the semaphore is acquired or return "
+           "true if it has not been acquired after the specified timeout.")
+      .def("release", &Semaphore::release, py::call_guard<py::gil_scoped_release>(),
+           "Release the semaphore.")
+      .def_property_readonly("capacity", &Semaphore::get_capacity, py::call_guard<py::gil_scoped_release>(),
+                             "Get the semaphore capacity.")
+      .def_property_readonly("would_block", &Semaphore::would_block, py::call_guard<py::gil_scoped_release>(),
+                             "Check whether trying to acquire the semaphore would block (in other word, checks whether "
+                             "this semaphore has capacity).")
+      // Allow semaphores to be automatically acquired/released with a context manager: `with semaphore: ...`
+      .def("__enter__", [](Semaphore* self){ self->acquire(); }, py::call_guard<py::gil_scoped_release>())
+      .def("__exit__", [](Semaphore* self){ self->release(); }, py::call_guard<py::gil_scoped_release>());
+
   /* 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(py::init<>(&Mutex::create), py::call_guard<py::gil_scoped_release>(), "Mutex constructor.")
       .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")
+      .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, const py::object&, const py::object&, const py::object&) { self->unlock(); },
@@ -797,10 +822,11 @@ PYBIND11_MODULE(simgrid, m)
   /* Class Barrier */
   py::class_<Barrier, BarrierPtr>(m, "Barrier",
                                   "A classical barrier, but blocking in the simulation world.")
-      .def(py::init<>(&Barrier::create), py::call_guard<py::gil_scoped_release>(), py::arg("expected_actors"))
+      .def(py::init<>(&Barrier::create), py::call_guard<py::gil_scoped_release>(), py::arg("expected_actors"),
+           "Barrier constructor.")
       .def("wait", &Barrier::wait, py::call_guard<py::gil_scoped_release>(),
            "Blocks into the barrier. Every waiting actors will be unlocked once the expected amount of actors reaches "
-           "the barrier");
+           "the barrier.");
 
   /* Class Actor */
   py::class_<simgrid::s4u::Actor, ActorPtr>(m, "Actor",