Skip to content
Snippets Groups Projects
Verified Commit 4b7cfe2a authored by orestis.malaspin's avatar orestis.malaspin
Browse files

translations and added slides for amelioration 1

parent 5cb435e6
No related branches found
No related tags found
No related merge requests found
Pipeline #38721 passed
--- --
title: "Runtimes, wakers et réacteurs-exécuteurs" title: "Runtimes, wakers et réacteurs-exécuteurs"
author: "Orestis Malaspinas" author: "Orestis Malaspinas"
date: "2025-03-24" date: "2025-03-24"
...@@ -111,7 +111,112 @@ fn main() { ...@@ -111,7 +111,112 @@ fn main() {
* On `poll()` la `Future` principale (dans le `main()`) * On `poll()` la `Future` principale (dans le `main()`)
* Cette `Future` doit `poll()` ses `Future` enfants et retourne que lorsque les deux sont `Ready()` * Cette `Future` doit `poll()` ses `Future` enfants et retourne que lorsque les deux sont `Ready()`
* On veut remplacer ces `poll()` continus, par une file d'évènements (cf. `04_syscall_event_queue.md`) * On veut remplacer ces `poll()` continus, par une file d'évènements (cf. `04_syscall_event_queue.md`)
* On veut plus de la boucle inifinie: mais utiliser `epoll()`
## Amélioration 1: éviter le sondage actif (3/)
```rust
static REGISTRY: OnceLock<Registry> = OnceLock::new();
pub fn registry() -> &'static Registry {
REGISTRY.get().expect("Called outside a runtime context")
}
pub struct Runtime {
poll: Poll,
}
```
* On veut plus de la boucle inifinie: mais utiliser `epoll()` * `Poll` est la file d'évènements de `mio` et constitue notre `Runtime`
* `Register` sert à enregistrer l'intérêt à une **source** d'évènements (ici `TcpStream`)
* Interaction avec le `Registry` que via la fonction `registry()` qui s'assure
## Amélioration 1: éviter le sondage actif (4/)
```rust
impl Runtime {
pub fn new() -> Self {
let poll = Poll::new().unwrap();
let registry = poll.registry().try_clone().unwrap();
REGISTRY.set(registry).unwrap();
Self { poll }
}
pub fn block_on<F>(&mut self, future: F) where F: Future<Output = String> {
let mut future = future;
while let PollState::NotReady = future.poll() {
println!("Schedule other tasks");
let mut events = Events::with_capacity(100);
self.poll.poll(&mut events, None).unwrap();
}
}
}
```
* `new()`: nouvelle instance de `Poll` et du `Registry` associé
* Intialisation du `REGISTRY` global lors de la création du `Runtime`
* `block_on()`: argument qui implémente `Future`
* `future.poll()` des `Future` jusqu'à ce qu'ils soient `Ready(_)`
* `poll.poll()` est réveillé lorsque les évènements liés à la file surviennent
* rien à faire avec `events`, mais c'est ici qu'on va "bloquer"
## Amélioration 1: éviter le sondage actif (5/)
```rust
fn poll(&mut self) -> PollState<Self::Output> {
if self.stream.is_none() {
println!("First poll - start operation");
self.write_request();
// Registering the Tcp stream as a source of events in the poll instance
runtime::registry()
.register(self.stream.as_mut().unwrap(), Token(0), Interest::READABLE)
.unwrap();
}
...
}
```
* Lors du premier appel à `poll()`:
* on enregistre l'intérêt au `TcpStream`
* on essaie de lire `TcpStream` et on arrive dans `WouldBlock` (les données sont pas prêtes)
* on retourne `NotReady`
* On retourne à `block_on()` dans le `main()` et on attend le prochain réveil pour rappeler `poll()`
## Amélioration 1: éviter le sondage actif (6/)
```console
Program starting
First poll - start operation
Data not ready
Schedule other tasks
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 15
connection: close
date: Mon, 24 Mar 2025 07:56:51 GMT
HelloAsyncAwait
First poll - start operation
Data not ready
Schedule other tasks
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 15
connection: close
date: Mon, 24 Mar 2025 07:56:52 GMT
HelloAsyncAwait
```
* Un seul appel à "Schedule other tasks" et "Data not ready"
* On a bien le réveil qui se fait quand les tâches sont prêtes à continuer (pas d'appel continu à "Schedule other tasks")
* Pas de travail "inutile"
## Amélioration 1: quelles améliorations encore
* On rend le contrôle à l'OS quand on appelle `Poll::poll()`
* On a parlé de réacteur - exécuteur, mais deux sont très fortement couplés via `Poll::poll()`
* On aimerait séparer le couplage de la file d'évènement
* On va ajouer un `Waker` comme dans le design des `Future` de Rust
* Faire un lien entre réacteur et exécuteur
* Ajouter un Réacteur qui va gérer les `Waker` qui va être responsable de la communication avec l'exécuteur
## Amélioration 2: ajouter un réacteur et un waker (1/)
...@@ -72,6 +72,7 @@ impl Future for HttpGetFuture { ...@@ -72,6 +72,7 @@ impl Future for HttpGetFuture {
if self.stream.is_none() { if self.stream.is_none() {
println!("First poll - start operation"); println!("First poll - start operation");
self.write_request(); self.write_request();
// Registering the Tcp stream as a source of events in the poll instance
runtime::registry() runtime::registry()
.register(self.stream.as_mut().unwrap(), Token(0), Interest::READABLE) .register(self.stream.as_mut().unwrap(), Token(0), Interest::READABLE)
.unwrap(); .unwrap();
......
...@@ -11,6 +11,33 @@ fn main() { ...@@ -11,6 +11,33 @@ fn main() {
let mut runtime = Runtime::new(); let mut runtime = Runtime::new();
runtime.block_on(future); runtime.block_on(future);
} }
// Our ptograms outputs the following. It is very important to note that there is only on Schedule
// other task and Data not ready calls that are shown. This is due to the event queue polling which
// only wakes the task when an event occured saving a lot of CPU cycles. In our older example we
// had to limit the number of calls in order to not get flooded with "Schedule other tasks"
// messages. This version is single threaded to keep things simple.
//
//Program starting
//First poll - start operation
//Data not ready
//Schedule other tasks
//HTTP/1.1 200 OK
//content-type: text/plain; charset=utf-8
//content-length: 15
//connection: close
//date: Mon, 24 Mar 2025 07:56:51 GMT
//
//HelloAsyncAwait
//First poll - start operation
//Data not ready
//Schedule other tasks
//HTTP/1.1 200 OK
//content-type: text/plain; charset=utf-8
//content-length: 15
//connection: close
//date: Mon, 24 Mar 2025 07:56:52 GMT
//
//HelloAsyncAwait
// ================================= // =================================
// We rewrite this: // We rewrite this:
......
use std::future;
use std::sync::OnceLock; use std::sync::OnceLock;
use mio::{Events, Poll, Registry}; use mio::{Events, Poll, Registry};
use crate::future::{Future, PollState}; use crate::future::{Future, PollState};
// Le Registry est la façon d'enregistrer notre intérêt pour une instance de Poll. En l'ocurrence, // The Registry is the way we register our interest in a Poll instance. In this case,
// cea sera d'enregistrer les évènements sur notre `TcpStram` quand nous ferons une requête GET. // it will register events on our `TcpStream` when we make a GET request.
// Ainsi, nous voudrons accéder au Registry depuis l'intérieur de HttpGetFuture sans avoir besoin // In this way, we'll be able to access the Registry from inside HttpGetFuture without having to
// de passer des références de partout. C'est un peu moins élégant, mais cela est plus simple pour // pass references all over the place. It's a little less elegant, but simpler for our API.
// notre API.
// //
// Ici nous utilisons un OnceLock<_> qui est une valeur dans laquelle on peut écrire qu'une seule fois // Here we're using an `OnceLock<_>`, which is a value that can only be written to once
// et qui est non-initialisée quand elle est crée. Cette version est thread safe contrairement à sa // and is uninitialized when created. This version is thread safe, unlike its
// soeur `OnceCell`. On veut intialiser le Registry que quand on démarre notre runtime et pas au // sister `OnceCell`. We want to intitialize the Registry only when we start our runtime and not at
// démarrage du programme. Si le Runtime n'as pas été initialisé, toute tentative d'accès dans // program startup. If the Runtime has not been initialized, any attempt to access it via
// http::get(), on aura donc un message d'erreur. Le registry pourra être accédé que par la // http::get() will result in an error. The registry can only be accessed via the
// fonction `registry()` // function `registry()` which throws an error if the Runtime was not properly initialized
static REGISTRY: OnceLock<Registry> = OnceLock::new(); static REGISTRY: OnceLock<Registry> = OnceLock::new();
pub fn registry() -> &'static Registry { pub fn registry() -> &'static Registry {
REGISTRY.get().expect("Called outside a runtime context") REGISTRY.get().expect("Called outside a runtime context")
} }
// Rien d'autre que notre instance de Poll (la file d'évènements) // Nothing but our Poll instance (the event queue)
pub struct Runtime { pub struct Runtime {
poll: Poll, poll: Poll,
} }
impl Runtime { impl Runtime {
// On crée une nouvelle instance de notre file d'évènements et on initialise notre registe et // We create a new instance of our event queue and initialize our registry and
// notre Runtime avec. Ici on a une instance de `poll` qui est clonable (on pourrait l'accéder // our Runtime with it. Here we have an instance of `poll` which is clonable (we could access it
// depuis plusieurs endroit), On stocke le registry comme une variable globale: c'est pas joli // from several locations). We store the registry as a global variable: not pretty
// mais efficace // but efficient to keep things as simple as possible
pub fn new() -> Self { pub fn new() -> Self {
let poll = Poll::new().unwrap(); let poll = Poll::new().unwrap();
// creation of a copy of the Regfistry used to register a source of events
let registry = poll.registry().try_clone().unwrap(); let registry = poll.registry().try_clone().unwrap();
REGISTRY.set(registry).unwrap(); REGISTRY.set(registry).unwrap();
Self { poll } Self { poll }
} }
// 1. On peut bloquer sur n'imorte quelle fonction qui implémente le trait `Future` // 1. Block on any function that implements the `Future` trait
// 2. On prnd l'ownership de future et on la rend mutable // 2. Take ownership of future and make it mutable
// 3. On loop jusqu'à ce que la `Future` soit ready et on `poll()` pour un évènement quelconque // 3. Loop until `Future` is ready and `poll()` for any event
// qui pourrait ne rien avoir à faire avec notre future actuelle // that may have nothing to do with our current future
// poll() ici attend qu'on ait un évènement READABLE dans le stream // poll() here waits until we have a READABLE event in the stream
pub fn block_on<F>(&mut self, future: F) pub fn block_on<F>(&mut self, future: F)
where where
F: Future<Output = String>, F: Future<Output = String>,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment