Le troisième visage du .await : un endroit où la tâche peut être interrompue.The third face of .await: a place where the task can be interrupted.
On a vu le .await sous deux angles : un point de suspension où l'exécuteur reprend la main (№1), un point de migration où la donnée tenue doit être Send (№2). En voici le troisième, et il clôt le volume : un point d'annulation. Un Future n'avance que tant qu'on le poll. Cesse de le poller — laisse-le tomber, drop-le — et il s'arrête à son dernier .await, définitivement : ce qu'il n'avait pas fini ne sera jamais fait. C'est radicalement différent des threads du Vol 7, qu'on ne pouvait pas interrompre proprement de l'extérieur. En async, l'annulation n'est pas un mécanisme spécial à câbler : elle est dans le modèle. Un timeout, une course entre deux requêtes, un serveur qu'on arrête — tout cela s'exprime en arrêtant de poller certains Futures. Annuler, c'est drop.We've seen .await from two angles: a suspension point where the executor takes back control (№1), a migration point where held data must be Send (№2). Here's the third, and it closes the volume: a cancellation point. A Future only advances while it's polled. Stop polling it — let it fall, drop it — and it stops at its last .await, for good: what it hadn't finished will never be done. This is radically different from Vol 7's threads, which you couldn't cleanly interrupt from outside. In async, cancellation isn't a special mechanism to wire up: it's in the model. A timeout, a race between two requests, a server you shut down — all of it is expressed by ceasing to poll certain Futures. To cancel is to drop.
use tokio::time::{timeout, Duration}; // timeout pilote `requete()` ; si 5 s passent, il LÂCHE le Future — qui s'arrête net. match timeout(Duration::from_secs(5), requete()).await { Ok(reponse) => traiter(reponse), Err(_) => println!("trop lent — la requête a été ANNULÉE"), } // annuler, ici, ce n'est PAS envoyer un signal : c'est cesser de poller le Future. // il s'arrête à son dernier .await, et ce qu'il n'avait pas fini ne sera jamais fait.
timeout n'envoie aucun signal d'interruption à requete(). Il fait courir deux choses — la requête et un minuteur — et, dès que le minuteur gagne, il laisse tomber le Future de la requête. Celui-ci, n'étant plus pollé, s'arrête là où il en était. C'est tout. Cette simplicité est une force — l'annulation est gratuite et universelle — mais elle cache un piège : si la requête s'arrête au milieu d'une opération qu'elle croyait atomique, l'état qu'elle laisse peut être incohérent. C'est le sujet du chapitre 4.timeout sends no interrupt signal to requete(). It races two things — the request and a timer — and, the moment the timer wins, it drops the request's Future. That one, no longer polled, stops where it was. That's all. This simplicity is a strength — cancellation is free and universal — but it hides a trap: if the request stops in the middle of an operation it believed atomic, the state it leaves can be inconsistent. That's chapter 4's subject.
« Si cette tâche s'arrête net à ce .await, l'état qu'elle laisse est-il cohérent ? » Puisque tout .await est un point où la tâche peut être abandonnée, chacun est une question à se poser. Tant qu'une opération est atomique du point de vue de l'annulation — soit elle finit, soit elle n'a rien entamé d'irréversible — l'annulation est sans danger. Dès qu'elle laisse un travail à moitié fait derrière un .await, il faut y regarder de près.'If this task stops dead at this .await, is the state it leaves consistent?' Since every .await is a point where the task can be abandoned, each is a question to ask. As long as an operation is atomic from cancellation's standpoint — either it finishes, or it has started nothing irreversible — cancellation is harmless. The moment it leaves work half-done behind a .await, look closely.
Au Vol 7, interrompre un thread de l'extérieur était quasi impossible proprement : aucune API portable pour le « tuer » sans laisser ses verrous et sa mémoire dans un état douteux. On en était réduit à un drapeau partagé que le thread devait vérifier lui-même. L'async renverse cela : comme un Future est une valeur qu'on drop, et que le drop déclenche le nettoyage (RAII, Vol 1), l'annulation devient une opération de premier ordre, sûre par défaut côté ressources. Le revers, c'est qu'elle peut survenir à n'importe quel .await, parfois là où on ne l'avait pas prévu.In Vol 7, interrupting a thread from outside was nearly impossible cleanly: no portable API to 'kill' it without leaving its locks and memory in a dubious state. You were reduced to a shared flag the thread had to check itself. Async flips this: since a Future is a value you drop, and dropping triggers cleanup (RAII, Vol 1), cancellation becomes a first-class operation, safe by default on the resource side. The flip side is that it can happen at any .await, sometimes where you didn't expect it.