Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
C
cours
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Programmation sysytème avancée
cours
Commits
cb553c55
Verified
Commit
cb553c55
authored
1 month ago
by
orestis.malaspin
Browse files
Options
Downloads
Patches
Plain Diff
added text
parent
18fc9c90
No related branches found
No related tags found
No related merge requests found
Checking pipeline status
Changes
1
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
09_pinning.md
+335
-1
335 additions, 1 deletion
09_pinning.md
with
335 additions
and
1 deletion
09_pinning.md
+
335
−
1
View file @
cb553c55
...
...
@@ -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
(
"
\n
BUFFER:
\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
(
"
\n
BUFFER:
\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!()`
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment