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

added text

parent 18fc9c90
No related branches found
No related tags found
No related merge requests found
Checking pipeline status
......@@ -93,6 +93,20 @@ coroutine fn async_main() {
## Le compteur 1/
```rust
impl Coroutine {
fn new() -> Self {
Self {
stack: Stack { counter: None },
state: State::Start,
}
}
}
```
## Le compteur 2/
```rust
State::Start => {
self.stack.counter = Some(0);
......@@ -119,7 +133,7 @@ State::Wait2(ref mut f2) => {
}
```
## Compteur 2/
## Compteur 3/
* `Start`: on initialise le compteur
* `Wait1`:
......@@ -132,3 +146,323 @@ State::Wait2(ref mut f2) => {
* on affiche sa velur à l'écran
* on "nettoie" la valeur (rien besoin de faire, car on a déjà `None` dans la pile)
# Partie 2: Ajouter des références
## Objectif: ajouter un buffer dans notre code `async`
```rust
coroutine fn async_main() {
let mut buffer = String::from("\nBUFFER:\n------\n");
let writer = &mut buffer;
let mut counter = 0;
println!("Program starting");
let txt = Http::get("/600/HelloAsyncWait").wait;
writeln!(writer, "{txt}").unwrap();
counter += 1;
let txt = Http::get("/400/HelloAsyncWait").wait;
writeln!(writer, "{txt}").unwrap();
writeln!(writer, "------").unwrap();
counter += 1;
println!("Received {} responses", counter);
println!("{buffer}");
}
```
* `buffer` sera une `Option<String>`
* `writer` est une `Option<*mut String>`
* Interdit de `Option<&mut String>`, car `writer` pointe sur `buffer`
* `writeln!()` permet d'écrire dans une autre cible que `println!()`
## Le `buffer` 1/
```rust
struct Coroutine {
stack: Stack,
state: State,
}
struct Stack {
counter: Option<usize>,
buffer: Option<String>,
writer: Option<*mut String>,
}
impl Coroutine {
fn new() -> Self {
Self {
stack: Stack { counter: None, buffer: None, writer: None },
state: State::Start,
}
}
}
```
* On étend notre `Stack` avec le `buffer` et `writer`
## Le `buffer` 2/
### `Start`
```rust
State::Start => {
self.stack.counter = Some(0);
self.stack.buffer = Some(String::from("\nBUFFER:\n------\n"));
self.stack.writer = Some(self.stack.buffer.as_mut().unwrap());
println!("Program starting");
let fut1 = Box::new(Http::get("/600/HelloAsyncWait"));
self.state = State::Wait1(fut1);
}
```
* On initialise `buffer/writer`
* On écrit dans le buffer via `writer`
* L'état est implicitement sauvé dans la struct `stack`
* `buffer.as_mut()` coercé en `*mut String`
### `Wait1`
```rust
State::Wait1(ref mut f1) => {
match f1.poll(waker) {
PollState::Ready(txt) => {
let mut counter = self.stack.counter.take().unwrap();
let writer = unsafe { &mut *self.stack.writer.take().unwrap() };
writeln!(writer, "{txt}").unwrap();
counter += 1;
let fut2 = Box::new(Http::get("/400/HelloAsyncWait"));
self.state = State::Wait2(fut2);
self.stack.counter = Some(counter);
self.stack.writer = Some(writer);
}
PollState::NotReady => break PollState::NotReady,
}
}
```
* On prend l'ownership du `writer`, et on écrit dedans
* On remet le `writer` dans la `stack`
* On est obligé de faire du `unsafe`
### `Wait2`
```rust
State::Wait2(ref mut f2) => {
match f2.poll(waker) {
PollState::Ready(txt) => {
let mut counter = self.stack.counter.take().unwrap();
let buffer = self.stack.buffer.as_ref().take().unwrap();
let writer = unsafe { &mut *self.stack.writer.take().unwrap() };
writeln!(writer, "{txt}").unwrap();
writeln!(writer, "------").unwrap();
counter += 1;
println!("Received {counter} responses");
println!("{buffer}");
self.state = State::Resolved;
let _ = self.stack.buffer.take();
break PollState::Ready(String::new());
}
PollState::NotReady => break PollState::NotReady,
}
}
```
* On prend une référence vers le `buffer`
* On **peut pas** prendre l'ownership du `buffer` => le `writer` serait invalidé
* On **prend l'ownership** de la référence vers le buffer
* On écrit dans le `writer` et affiche le `buffer`
* On `drop()` le bufffer à la fin pour libérer les ressources et éviter de garder les choses inutiles en mémoire
# Apparté: optimisation
## Optimisation
* Pour éviter de `poll()` trop souvent, on pourrait optimiser un tout petit peu le code
* On modifie `block_on()`
```rust
pub fn block_on<F>(&mut self, future: F)
where
F: Future<Output = String> + 'static,
{
let waker = self.get_waker(usize::MAX);
let mut future = future;
match future.poll(&waker) {
PollState::NotReady => (),
PollState::Ready(_) => return,
}
spawn(future);
loop {
while let Some(id) = self.pop_ready() {
...
```
* Si les `Future` parent est déjà `Ready` on a rien besoin de faire.
## Le drame
```console
Program starting
First poll - start operation
Data not ready
Data not ready
main: 1, pending tasks. Sleep until notified.
First poll - start operation
Data not ready
main: 1, pending tasks. Sleep until notified.
Received 2 responses
/400/HelloAsyncW
free(): double free detected in tcache 2
```
* Hum, hum, `free(): double free detected in tcache 2`
## Ok.... Il s'est passé quoi?
1. On a un `Future` en argument de `block_on()` et tout va bien
2. On appelle `poll()` une première fois
* le `Future` est sur la pile de `block_on()`
* on appelle `poll()` et on est dans l'état `Start`
* initialisation de la structure auto-référencée qui fait pointer le `writer` sur le `buffer` qui est sur la pile
3. On retourne `NotReady` et le future est placé dans la `HashMap<usize, Box<dyn Future>>` de `Executor`
4. Le `Future` est désormais sur la pile
5. A l'appel suivant de `poll()` on essaie de lire le `buffer` via `writer` qui contient encore l'ancienne adresse...
# Partie 4: Le Pinning
## Le Pinning
* Trouver un moyen de **fixer** la mémoire pour avoir la **garantie** de pourvoir partager un état
* Aucune garantie de l'OS que des données restent au même endroit
* En Rust: un type `Pin<T>` et un trait `Unpin`
* Un trait `Unpin` implémenté pour un type fait que `Pin<T>` n'a aucun effet
* `Pin<T>` un type qui est pas `Unpin` darantit qu'il bouge pas en mémoire
* `Pin<T>` est un *wrapper* autour des références et pointeurs intelligents
* Épingler un type qui n'est pas `Unpin` garantit que la valeur va rester à la même place en mémoire
## Un exemple de Pinning 1/
```rust
struct MaybeSelfRef {
data: usize,
self_ref: Option<*mut usize>,
_pin: PhantomPinned,
}
```
* Des données, une auto-référence
* Un `PhantomPinned` qui indique au compilateur que rien ne devra bouger
## Un exemple de Pinning 2/
```rust
impl MaybeSelfRef {
fn new() -> Self {
Self {
data: 0,
self_ref: None,
_pin: PhantomPinned::default(),
}
}
```
* On crée une struct poar "défaut"
* Pas encore auto-référencée
## Un exemple de Pinning 3/
```rust
impl MaybeSelfRef {
fn init(self: Pin<&mut Self>) {
unsafe {
let Self { data, self_ref, .. } = self.get_unchecked_mut();
*self_ref = Some(data);
}
}
}
```
* La structure devient auto-référencée que lorsqu'on appelle `init()`
* Le type en argument est `Pin<&mut Self>`: **Pin projection**
## Un exemple de Pinning 4/
```rust
impl MaybeSelfRef {
fn self_ref(self: Pin<&mut Self>) -> Option<&mut usize> {
unsafe { self.get_unchecked_mut().self_ref.map(|sr| &mut *sr) }
}
}
```
* On cache le pointeur et le `unsafe` dans une fonction
* Transformer un pointeur en référence est intrisèquement `unsafe` car on sait pas si le pointeur est valide
## Le Pinning sur le tas
```rust
fn main() {
let mut x = Box::pin(MaybeSelfRef::new());
x.as_mut().init();
println!("Heap Pinning data {}", x.as_ref().data);
*x.as_mut().self_ref().unwrap() = 10;
println!("Heap Pinning modified {}", x.as_ref().data);
}
```
* On crée un `Box::pin()` (pointeur de données sur le tas)
* On initialise notre instance
* On modifie l'instance avec la `self_ref()`
* C'est "simple" d'épingler sur le tas (on peut mettre la valeur n'importe où)
## Le Pinning sur la pile 1/
```rust
fn main() {
let mut x = MaybeSelfRef::new();
let mut x = unsafe { Pin::new_unchecked(&mut x) };
x.as_mut().init();
println!(" Pinning data {}", x.as_ref().data);
*x.as_mut().self_ref().unwrap() = 10;
println!("Heap Pinning modified {}", x.as_ref().data);
}
```
* Presque pareil.... Sauf que c'est `unsafe` de créer l'instance épinglée `x`
* C'est difficile de garantir la position de `x` dans la pile parce que c'est structuré (faut une "bonne place")
* Si on fait cela à l'intérieur d'une fonction, on peut pas garantir que les données seront là (on drop tout à la sortie)
* C'est `unsafe` car les garanties de Rust peuvent pas être satisfaites
## Le Pinning sur la pile 2/
```rust
fn main() {
println!("Problem with Stack pinning");
let mut x = MaybeSelfRef::new();
let mut y = MaybeSelfRef::new();
{
unsafe {
let mut x = Pin::new_unchecked(&mut x);
x.as_mut().init();
*x.as_mut().self_ref().unwrap() = 10;
};
}
swap(&mut x, &mut y);
}
```
* On cache l'épinglage de `x` dans un bloc séparé.
* Quand on quitte `{}` on peut échanger les références en `safe`
* Ici `y` contiendra l'adresse de `x.data` et non celle de `y.data`
## Le Pinning sur la pile 3/
```rust
fn main() {
let mut x = pin!(MaybeSelfRef::new());
MaybeSelfRef::init(x.as_mut());
println!("{}", x.as_ref().data);
*x.as_mut().self_ref().unwrap() = 2;
println!("{}", x.as_ref().data);
}
```
* La macro `pin!()` nous sauve
* Si on essaie de refaire l'exemple précent, on a une erreur de compilation
* Si on veut absolument faire un `Pin` sur la pile, utiliser `pin!()`
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment