Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
[sonar] Declare move constructors with "noexcept".
[simgrid.git] / include / simgrid / simix / blocking_simcall.hpp
1 /* Copyright (c) 2016-2020. The SimGrid Team. All rights reserved.          */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5
6 #ifndef SIMGRID_SIMIX_BLOCKING_SIMCALL_HPP
7 #define SIMGRID_SIMIX_BLOCKING_SIMCALL_HPP
8
9 #include <exception>
10 #include <functional>
11 #include <future>
12 #include <utility>
13
14 #include <xbt/sysdep.h>
15
16 #include <xbt/future.hpp>
17 #include <simgrid/kernel/future.hpp>
18 #include <simgrid/simix.h>
19 #include <simgrid/simix.hpp>
20
21 namespace simgrid {
22 namespace simix {
23
24 XBT_PUBLIC void unblock(smx_actor_t process);
25
26 /** Execute some code in kernel mode and wakes up the actor when
27  *  the result is available.
28  *
29  * It is given a callback which is executed in the SimGrid kernel and
30  * returns a `simgrid::kernel::Future<T>`. The kernel blocks the actor
31  * until the Future is ready and:
32  *
33  *  - either returns the value wrapped in the future to the actor
34  *
35  *  - or raises the exception stored in the future in the actor.
36  *
37  * This can be used to implement blocking calls without adding new simcalls.
38  * One downside of this approach is that we don't have any semantic on what
39  * the actor is waiting. This might be a problem for the model-checker and
40  * we'll have to devise a way to make it work.
41  *
42  * @param     code Kernel code returning a `simgrid::kernel::Future<T>`
43  * @return         Value of the kernel future
44  * @exception      Exception from the kernel future
45  */
46 template <class F> auto kernel_sync(F code) -> decltype(code().get())
47 {
48   typedef decltype(code().get()) T;
49   if (SIMIX_is_maestro())
50     xbt_die("Can't execute blocking call in kernel mode");
51
52   smx_actor_t self = SIMIX_process_self();
53   simgrid::xbt::Result<T> result;
54
55   simcall_run_blocking(
56       [&result, self, &code] {
57         try {
58           auto future = code();
59           future.then_([&result, self](std::shared_ptr<simgrid::kernel::FutureState<T>>&& value) {
60             simgrid::xbt::set_promise(result, simgrid::kernel::Future<T>(std::move(value)));
61             simgrid::simix::unblock(self);
62           });
63         } catch (...) {
64           result.set_exception(std::current_exception());
65           simgrid::simix::unblock(self);
66         }
67       },
68       nullptr);
69   return result.get();
70 }
71
72 /** A blocking (`wait()`-based) future for SIMIX processes */
73 // TODO, .wait_for
74 // TODO, .wait_until
75 // TODO, SharedFuture
76 // TODO, simgrid::simix::when_all - wait for all future to be ready (this one is simple!)
77 // TODO, simgrid::simix::when_any - wait for any future to be ready
78 template <class T>
79 class Future {
80 public:
81   Future() { /* Nothing to do*/}
82   explicit Future(simgrid::kernel::Future<T> future) : future_(std::move(future)) {}
83   Future(Future&&) noexcept = default;
84   Future& operator=(Future&&) noexcept = default;
85
86   bool valid() const { return future_.valid(); }
87   T get()
88   {
89     if (not valid())
90       throw std::future_error(std::future_errc::no_state);
91     smx_actor_t self = SIMIX_process_self();
92     simgrid::xbt::Result<T> result;
93     simcall_run_blocking(
94         [this, &result, self] {
95           try {
96             // When the kernel future is ready...
97             this->future_.then_([&result, self](std::shared_ptr<simgrid::kernel::FutureState<T>>&& value) {
98               // ... wake up the process with the result of the kernel future.
99               simgrid::xbt::set_promise(result, simgrid::kernel::Future<T>(std::move(value)));
100               simgrid::simix::unblock(self);
101             });
102           } catch (...) {
103             result.set_exception(std::current_exception());
104             simgrid::simix::unblock(self);
105           }
106         },
107         nullptr);
108     return result.get();
109   }
110   bool is_ready() const
111   {
112     if (not valid())
113       throw std::future_error(std::future_errc::no_state);
114     return future_.is_ready();
115   }
116   void wait()
117   {
118     // The future is ready! We don't have to wait:
119     if (this->is_ready())
120       return;
121     // The future is not ready. We have to delegate to the SimGrid kernel:
122     std::exception_ptr exception;
123     smx_actor_t self = SIMIX_process_self();
124     simcall_run_blocking(
125         [this, &exception, self] {
126           try {
127             // When the kernel future is ready...
128             this->future_.then_([this, self](std::shared_ptr<simgrid::kernel::FutureState<T>>&& value) {
129               // ...store it the simix kernel and wake up.
130               this->future_ = simgrid::kernel::Future<T>(std::move(value));
131               simgrid::simix::unblock(self);
132             });
133           } catch (...) {
134             exception = std::current_exception();
135             simgrid::simix::unblock(self);
136           }
137         },
138         nullptr);
139   }
140
141 private:
142   // We wrap an event-based kernel future:
143   simgrid::kernel::Future<T> future_;
144 };
145
146 /** Start some asynchronous work
147  *
148  *  @param code SimGrid kernel code which returns a simgrid::kernel::Future
149  *  @return     Actor future
150  */
151 template <class F> auto kernel_async(F code) -> Future<decltype(code().get())>
152 {
153   typedef decltype(code().get()) T;
154
155   // Execute the code in the kernel and get the kernel future:
156   simgrid::kernel::Future<T> future = simgrid::kernel::actor::simcall(std::move(code));
157
158   // Wrap the kernel future in a actor future:
159   return simgrid::simix::Future<T>(std::move(future));
160 }
161 }
162 }
163
164 #endif