mandatory to the model-checker. The simcalls, representing actors'
actions, are the transitions of the formal system. Verifying the
system requires to manipulate these transitions explicitly. This also
-allows to run safely the actors in parallel, even if this is less
+allows one to run the actors safely in parallel, even if this is less
commonly used by our users.
So, the key ideas here are:
Our futures are based on the C++ Concurrency Technical Specification
API, with a few differences:
- - The simulation kernel is single-threaded so we do not need
+ - The simulation kernel is single-threaded so we do not need
inter-thread synchronization for our futures.
- As the simulation kernel cannot block, `f.wait()` is not meaningful
@code{cpp}
template<class T>
template<class F>
-auto simgrid::kernel::Future<T>::thenNoUnwrap(F continuation)
+auto simgrid::kernel::Future<T>::then_no_unwrap(F continuation)
-> Future<decltype(continuation(std::move(*this)))>
{
typedef decltype(continuation(std::move(*this))) R;
template<class T>
T simgrid::kernel::FutureState<T>::get()
{
- if (status_ != FutureStatus::ready)
- xbt_die("Deadlock: this future is not ready");
+ xbt_assert(status_ == FutureStatus::ready, "Deadlock: this future is not ready");
status_ = FutureStatus::done;
if (exception_) {
std::exception_ptr exception = std::move(exception_);
simulation kernel and yield the control until the request is
fulfilled. The performance requirements are very high because
the actors usually do an inordinate amount of simcalls during the
-simulation.
+simulation.
As for real syscalls, the basic idea is to write the wanted call and
its arguments in a memory area that is specific to the actor, and
# This looks like C++ but it is a basic IDL-like language
# (one definition per line) parsed by a python script:
-void process_kill(smx_process_t process);
+void process_kill(smx_actor_t process);
void process_killall(int reset_pid);
-void process_cleanup(smx_process_t process) [[nohandler]];
-void process_suspend(smx_process_t process) [[block]];
-void process_resume(smx_process_t process);
-void process_set_host(smx_process_t process, sg_host_t dest);
-int process_is_suspended(smx_process_t process) [[nohandler]];
-int process_join(smx_process_t process, double timeout) [[block]];
+void process_cleanup(smx_actor_t process) [[nohandler]];
+void process_suspend(smx_actor_t process) [[block]];
+void process_resume(smx_actor_t process);
+void process_set_host(smx_actor_t process, sg_host_t dest);
+int process_is_suspended(smx_actor_t process) [[nohandler]];
+int process_join(smx_actor_t process, double timeout) [[block]];
int process_sleep(double duration) [[block]];
smx_mutex_t mutex_init();
@code{cpp}
struct s_smx_simcall {
// Simcall number:
- e_smx_simcall_t call;
+ Simcall call;
// Issuing actor:
- smx_process_t issuer;
+ smx_actor_t issuer;
// Arguments of the simcall:
union u_smx_scalar args[11];
// Result of the simcall:
union u_smx_scalar result;
// Some additional stuff:
smx_timer_t timer;
- int mc_value;
};
@endcode
responsible for wrapping the parameters in the `struct s_smx_simcall`;
and wrapping out the result;
-* [accessors](https://github.com/simgrid/simgrid/blob/4ae2fd01d8cc55bf83654e29f294335e3cb1f022/src/simix/popping_accessors.h)
+* [accessors](https://github.com/simgrid/simgrid/blob/4ae2fd01d8cc55bf83654e29f294335e3cb1f022/src/simix/popping_accessors.hpp)
to get/set values of of `struct s_smx_simcall`;
* a simulation-kernel-side [big switch](https://github.com/simgrid/simgrid/blob/4ae2fd01d8cc55bf83654e29f294335e3cb1f022/src/simix/popping_generated.cpp#L106)
@code{cpp}
xbt_dict_t Host::properties() {
return simgrid::simix::kernelImmediate([&] {
- simgrid::surf::HostImpl* surf_host =
- this->extension<simgrid::surf::HostImpl>();
- return surf_host->getProperties();
+ simgrid::kernel::resource::HostImpl* host =
+ this->extension<simgrid::kernel::resource::HostImpl>();
+ return host->getProperties();
});
}
@endcode
`simgrid::simix::unblock(actor)`) when the operation is completed.
This is wrapped in a higher-level primitive as well. The
-`kernelSync()` function expects a function-object which is executed
+`kernel_sync()` function expects a function-object which is executed
immediately in the simulation kernel and returns a `Future<T>`. The
simulator blocks the actor and resumes it when the `Future<T>` becomes
ready with its result:
@code{cpp}
template<class F>
-auto kernelSync(F code) -> decltype(code().get())
+auto kernel_sync(F code) -> decltype(code().get())
{
typedef decltype(code().get()) T;
- if (SIMIX_is_maestro())
- xbt_die("Can't execute blocking call in kernel mode");
+ xbt_assert(not SIMIX_is_maestro(), "Can't execute blocking call in kernel mode");
- smx_process_t self = SIMIX_process_self();
+ auto self = simgrid::kernel::actor::ActorImpl::self();
simgrid::xbt::Result<T> result;
simcall_run_blocking([&result, self, &code]{
A contrived example of this would be:
@code{cpp}
-int res = simgrid::simix::kernelSync([&] {
+int res = simgrid::simix::kernel_sync([&] {
return kernel_wait_until(30).then(
[](simgrid::kernel::Future<void> future) {
return 42;
### Asynchronous operations {#uhood_switch_v2_async}
-We can write the related `kernelAsync()` which wakes up the actor immediately
+We can write the related `kernel_async()` which wakes up the actor immediately
and returns a future to the actor. As this future is used in the actor context,
it is a different future
(`simgrid::simix::Future` instead of `simgrid::kernel::Future`)
{
if (!valid())
throw std::future_error(std::future_errc::no_state);
- smx_process_t self = SIMIX_process_self();
+ auto self = simgrid::kernel::actor::ActorImpl::self();
simgrid::xbt::Result<T> result;
simcall_run_blocking([this, &result, self]{
try {
}
@endcode
-`kernelAsync()` simply :wink: calls `kernelImmediate()` and wraps the
+`kernel_async()` simply :wink: calls `kernelImmediate()` and wraps the
`simgrid::kernel::Future` into a `simgrid::simix::Future`:
@code{cpp}
template<class F>
-auto kernelAsync(F code)
+auto kernel_async(F code)
-> Future<decltype(code().get())>
{
typedef decltype(code().get()) T;
A contrived example of this would be:
@code{cpp}
-simgrid::simix::Future<int> future = simgrid::simix::kernelSync([&] {
+simgrid::simix::Future<int> future = simgrid::simix::kernel_sync([&] {
return kernel_wait_until(30).then(
[](simgrid::kernel::Future<void> future) {
return 42;
int res = future.get();
@endcode
-`kernelSync()` could be rewritten as:
+`kernel_sync()` could be rewritten as:
@code{cpp}
template<class F>
-auto kernelSync(F code) -> decltype(code().get())
+auto kernel_sync(F code) -> decltype(code().get())
{
- return kernelAsync(std::move(code)).get();
+ return kernel_async(std::move(code)).get();
}
@endcode
The semantic is equivalent but this form would require two simcalls
-instead of one to do the same job (one in `kernelAsync()` and one in
+instead of one to do the same job (one in `kernel_async()` and one in
`.get()`).
## Mutexes and condition variables
-## Mutexes
-
-SimGrid has had a C-based API for mutexes and condition variables for
-some time. These mutexes are different from the standard
-system-level mutex (`std::mutex`, `pthread_mutex_t`, etc.) because
-they work at simulation-level. Locking on a simulation mutex does
-not block the thread directly but makes a simcall
-(`simcall_mutex_lock()`) which asks the simulation kernel to wake the calling
-actor when it can get ownership of the mutex. Blocking directly at the
-OS level would deadlock the simulation.
-
-Reusing the C++ standard API for our simulation mutexes has many
-benefits:
-
- * it makes it easier for people familiar with the `std::mutex` to
- understand and use SimGrid mutexes;
-
- * we can benefit from a proven API;
-
- * we can reuse from generic library code in SimGrid.
-
-We defined a reference-counted `Mutex` class for this (which supports
-the [`Lockable`](http://en.cppreference.com/w/cpp/concept/Lockable)
-requirements, see
-[`[thread.req.lockable.req]`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf#page=1175)
-in the C++14 standard):
-
-@code{cpp}
-class Mutex {
- friend ConditionVariable;
-private:
- friend simgrid::simix::Mutex;
- simgrid::simix::Mutex* mutex_;
- Mutex(simgrid::simix::Mutex* mutex) : mutex_(mutex) {}
-public:
-
- friend void intrusive_ptr_add_ref(Mutex* mutex);
- friend void intrusive_ptr_release(Mutex* mutex);
- using Ptr = boost::intrusive_ptr<Mutex>;
-
- // No copy:
- Mutex(Mutex const&) = delete;
- Mutex& operator=(Mutex const&) = delete;
-
- static Ptr createMutex();
-
-public:
- void lock();
- void unlock();
- bool try_lock();
-};
-@endcode
-
-The methods are simply wrappers around existing simcalls:
-
-@code{cpp}
-void Mutex::lock()
-{
- simcall_mutex_lock(mutex_);
-}
-@endcode
-
-Using the same API as `std::mutex` (`Lockable`) means we can use existing
-C++-standard code such as `std::unique_lock<Mutex>` or
-`std::lock_guard<Mutex>` for exception-safe mutex handling[^lock]:
-
-@code{cpp}
-{
- std::lock_guard<simgrid::s4u::Mutex> lock(*mutex);
- sum += 1;
-}
-@endcode
-
### Condition Variables
Similarly SimGrid already had simulation-level condition variables
@code{cpp}
class ConditionVariable {
private:
- friend s_smx_cond;
+ friend s_smx_cond_t;
smx_cond_t cond_;
ConditionVariable(smx_cond_t cond) : cond_(cond) {}
public:
void notify_one();
void notify_all();
-
+
};
@endcode
simcall_cond_wait_timeout(cond_, lock.mutex()->mutex_, timeout);
return std::cv_status::no_timeout;
}
- catch (xbt_ex& e) {
-
+ catch (const simgrid::TimeoutException& e) {
// If the exception was a timeout, we have to take the lock again:
- if (e.category == timeout_error) {
- try {
- lock.mutex()->lock();
- return std::cv_status::timeout;
- }
- catch (...) {
- std::terminate();
- }
+ try {
+ lock.mutex()->lock();
+ return std::cv_status::timeout;
+ }
+ catch (...) {
+ std::terminate();
}
-
- std::terminate();
}
catch (...) {
std::terminate();
double duration, P pred)
{
return this->wait_until(lock,
- SIMIX_get_clock() + duration, std::move(pred));
+ simgrid::s4u::Engine::get_clock() + duration, std::move(pred));
}
@endcode
* the second one is a wait-based (`future.get()`) future used in the actors
which waits using a simcall.
-These futures are used to implement `kernelSync()` and `kernelAsync()` which
+These futures are used to implement `kernel_sync()` and `kernel_async()` which
expose asynchronous operations in the simulation kernel to the actors.
In addition, we wrote variations of some other C++ standard library
classes (`SimulationClock`, `Mutex`, `ConditionVariable`) which work in
the simulation:
-
+
* using simulated time;
* using simcalls for synchronisation.
This type of approach might be useful for other libraries which define
their own contexts. An example of this is
-[Mordor](https://github.com/mozy/mordor), a I/O library using fibers
+[Mordor](https://github.com/mozy/mordor), an I/O library using fibers
(cooperative scheduling): it implements cooperative/fiber
[mutex](https://github.com/mozy/mordor/blob/4803b6343aee531bfc3588ffc26a0d0fdf14b274/mordor/fibersynchronization.h#L70),
[recursive
@code{cpp}
template<class T>
class Result {
- enum class ResultStatus {
- invalid,
- value,
- exception,
- };
public:
- Result();
- ~Result();
- Result(Result const& that);
- Result& operator=(Result const& that);
- Result(Result&& that);
- Result& operator=(Result&& that);
bool is_valid() const;
- void reset();
void set_exception(std::exception_ptr e);
void set_value(T&& value);
void set_value(T const& value);
T get();
private:
- ResultStatus status_ = ResultStatus::invalid;
- union {
- T value_;
- std::exception_ptr exception_;
- };
+ boost::variant<boost::blank, T, std::exception_ptr> value_;
};
@endcode~
@endcode
-## Notes
+## Notes
[^getcompared]:
in the simulation which we would like to avoid.
`std::try_lock()` should be safe to use though.
-*/
\ No newline at end of file
+*/