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

I think it's over

parent 48cdf2f3
No related branches found
No related tags found
No related merge requests found
Pipeline #38470 passed
......@@ -295,9 +295,233 @@ impl Future for HttpGetFuture {
3. `stream == Some() && read == Ok(0)` => `Resolved`
* Pas de modélisation explicite ici
## Exécution 1/
```console
Program starting
First poll - start operation
Schedule other tasks
Schedule other tasks
Schedule other tasks
Schedule other tasks
Schedule other tasks
Schedule other tasks
Schedule other tasks
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 11
connection: close
date: Mon, 17 Mar 2025 08:03:55 GMT
HelloWorld1
First poll - start operation
Schedule other tasks
Schedule other tasks
Schedule other tasks
Schedule other tasks
Schedule other tasks
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 11
connection: close
date: Mon, 17 Mar 2025 08:03:55 GMT
HelloWorld2
```
## Exécution 2/
* On a bien le bon nombre de `poll()` pour chaque tâche
* On a bien les deux requêtes `HelloWorld1` et `HelloWorld2`
* Les deux tâches sont exécutées séquentiellement
## Problème?
* C'est pas très simple à utiliser
* Impossible à généraliser
* Verbeux
* En un mot: beurk!
## Généralisation
* Il faut un support du compilateur
* Nécessité d'avoir des mots clés: `coroutine/wait`
* Utilisation de l'outil `corofy`: <https://github.com/PacktPublishing/Asynchronous-Programming-in-Rust/tree/main/ch07>
* Outil très simplifié qui crée la machine d'état et les attentes
## Utilisation de corofy
```console
cargo install --path .
```
* Syntaxe `coroutine/wait`
## Le nouveau `main`
```rust
fn get_path(i: usize) -> String {
format!("/{}/HelloWorld{i}", i * 1000)
}
coroutine fn async_main() {
println!("Program starting");
let txt = Http::get(&get_path(0)).wait;
println!("{txt}");
let txt = Http::get(&get_path(1)).wait;
println!("{txt}");
let txt = Http::get(&get_path(2)).wait;
println!("{txt}");
let txt = Http::get(&get_path(3)).wait;
println!("{txt}");
let txt = Http::get(&get_path(4)).wait;
println!("{txt}");
}
fn main() {
let start = Instant::now();
let mut future = async_main();
loop {
match future.poll() {
PollState::NotReady => (),
PollState::Ready(_) => break,
}
}
println!("Elapsed time: {}", start.elapsed().as_millis());
}
```
## Conversion 1/
```rust
enum State0 {
Start,
Wait1(Box<dyn Future<Output = String>>),
Wait2(Box<dyn Future<Output = String>>),
Wait3(Box<dyn Future<Output = String>>),
Wait4(Box<dyn Future<Output = String>>),
Wait5(Box<dyn Future<Output = String>>),
Resolved,
}
```
## Conversion 2/
```rust
fn poll(&mut self) -> PollState<Self::Output> {
loop {
match self.state {
State0::Start => {
println!("Program starting");
let fut1 = Box::new( Http::get(&get_path(0)));
self.state = State0::Wait1(fut1);
}
State0::Wait1(ref mut f1) => {
match f1.poll() {
PollState::Ready(txt) => {
println!("{txt}");
let fut2 = Box::new( Http::get(&get_path(1)));
self.state = State0::Wait2(fut2);
}
PollState::NotReady => break PollState::NotReady,
}
}
// ...
}
}
}
```
* La syntaxe `async/await` fait des choses similaires automatiquement (en beaucoup mieux)
## Coroutines et concurrence
* On a besoin d'un `join_all()` des `Future`
* Idée: parcourir les `Future` et les `poll()` à tour de rôle jusqu'à ce que toutes soient `Ready(_)`
* On stocke les `Future` dans une liste, ainsi que leur état (terminé ou non)
```rust
```
## Le `join_all` 1/
```rust
pub struct JoinAll<F: Future> {
futures: Vec<(bool, F)>,
finished_count: usize,
}
pub fn join_all<F: Future>(futures: Vec<F>) -> JoinAll<F> {
let futures = futures.into_iter().map(|f| (false, f)).collect();
JoinAll {
futures,
finished_count: 0,
}
}
```
* `futures` contient les `Future` et si chacune est terminée ou pas
* On veut connaître combien sont terminées ou non
* On transforme la liste de `Future` en tuple et on y va!
## Le `join_all` 2/
```rust
coroutine fn async_main() {
println!("Program starting");
let mut futures = vec![];
for i in 0..5 {
futures.push(request(i));
}
future::join_all(futures).wait;
}
fn main() {
let start = Instant::now();
let mut future = async_main();
loop {
match future.poll() {
PollState::NotReady => (),
PollState::Ready(_) => break,
}
}
println!("Elapsed time: {}", start.elapsed().as_millis());
}
```
## Le `join_all()` 3/
```rust
impl<F: Future> Future for JoinAll<F> {
type Output = String;
fn poll(&mut self) -> PollState<Self::Output> {
for (finished, future) in self.futures.iter_mut() {
if *finished {
continue;
}
match future.poll() {
PollState::Ready(_) => {
*finished = true;
self.finished_count += 1;
}
PollState::NotReady => continue,
}
}
if self.finished_count == self.futures.len() {
PollState::Ready(String::new())
} else {
PollState::NotReady
}
}
}
```
* Si `finished` => on continue
* Sinon: on `poll()` chaque `Future`:
* Si `Ready` => on marque le `Future` comme terminé
* Sinon on `continue`
* Quand tous sont terminés le `JoinAll` est `Ready`, sinon `NotReady`
## Conclusion
* Les coroutines sans pile, ne sont pas pré-emptables (on peut pas suspendre leur exécution à l'import quel moment)
* En l'absence d'une pile, on a pas les informations nécessaire pour arrêter le processus n'importe où
* On peut suspendre l'exécution qu'aux points `wait`
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment