Cél és előfeltétel
A határidő nem varázsolja félbe egy tetszőleges függvény végrehajtását. Ebben a leckében a feldolgozó maga ellenőrzi, hogy folytathatja-e a munkát. Előfeltétel a korlátos újrapróbálás és az explicit állapotgép. A példák egyetlen szálon, egész számlálóval szimulált idővel futnak; nem indítanak háttérmunkát és nem várnak valódi időre.
A kooperatív megszakítás azt jelenti, hogy a kód ismert ellenőrzési pontokon reagál egy kérésre. Két ilyen pont között a munka folytatódik. Egy lassú, blokkoló vagy ellenőrzés nélkül futó műveletet a puszta jelző nem szakít félbe. A megszakítási válaszidő ezért a munkadarabok hosszától és a ténylegesen megszakítható műveletektől is függ, nem csak egy bool létezésétől.
A leállítási ok és a haladás külön adat
A végrehajtás befejeződhet teljes sikerrel, lejárt határidővel vagy megszakítási kérés miatt. Az is fontos, mennyi munka történt már meg. Ha csak egyetlen Err eredményt adsz vissza, elveszhet a részleges haladás jelentése. A mai feladatok ezért a megfigyelt állapotot és a megállás okát együtt mutatják meg; ez nem jelenti a már elvégzett lépések visszavonását.
A határidő kizáró: amikor most egyenlő a határral, már nem kezdünk új munkát. A megszakítási kérésnek az első feladatban elsőbbséget adunk, ha mindkettő fennáll. Ez üzleti szerződés, nem a Rust automatikus szabálya. Ha más elsőbbséget szeretnél, azt a tesztben is nevezd meg; különben a felhasználó ugyanarra az állapotra véletlenszerűnek tűnő okot láthat.
1. feladat: fogalmazd meg az indulás feltételét
Írj kapufüggvényt most, határidő és megszakítási jelző alapján. A határ előtti időt fogadja el; az egyenlőség már lejárat legyen. Egyidejű lejárat és megszakítás esetén a megszakítást jelezze. A tesztek ne a gép órájától függjenek.
Megoldás
#[derive(Debug, PartialEq, Eq)]
enum Stop { Cancelled, Deadline }
fn allowed(now: u64, deadline: u64, cancelled: bool) -> Result<(), Stop> {
if cancelled { return Err(Stop::Cancelled); }
if now >= deadline { return Err(Stop::Deadline); }
Ok(())
}
fn main() {
assert_eq!(allowed(4, 5, false), Ok(()));
assert_eq!(allowed(5, 5, false), Err(Stop::Deadline));
assert_eq!(allowed(6, 5, false), Err(Stop::Deadline));
assert_eq!(allowed(6, 5, true), Err(Stop::Cancelled));
}
A kapu egy döntési pillanatra vonatkozik. Az Ok nem jelent korlátlan jogosultságot a teljes hátralévő munkára. Egy később végrehajtott hosszú művelet alatt lejárhat az idő vagy érkezhet megszakítás. Ezért a kaput ott is újra kell értékelni, ahol a következő biztonságosan elhagyható munkadarab kezdődne.
2. feladat: látszódjon a két ellenőrzés közötti túlfutás
Egy munkadarabhoz szimulált költség tartozik. Csak a darabok határán ellenőrizd a határidőt, és add vissza az elvégzett darabok számát is. Három időegységes munkák ötegységes határnál két darabig juthatnak: a második már átlép a határon, mielőtt újra ellenőrizhetnénk.
Megoldás
#[derive(Debug, PartialEq, Eq)]
enum End { Complete, Deadline, ClockOverflow }
fn process(costs: &[u64], deadline: u64) -> (usize, End) {
let mut now = 0_u64;
let mut completed = 0;
if now >= deadline { return (0, End::Deadline); }
for cost in costs {
if now >= deadline { return (completed, End::Deadline); }
let Some(next) = now.checked_add(*cost) else {
return (completed, End::ClockOverflow);
};
now = next;
completed += 1;
}
if now >= deadline { (completed, End::Deadline) }
else { (completed, End::Complete) }
}
fn main() {
assert_eq!(process(&[3, 3, 3], 5), (2, End::Deadline));
assert_eq!(process(&[2, 2], 5), (2, End::Complete));
assert_eq!(process(&[], 5), (0, End::Complete));
assert_eq!(process(&[1], 0), (0, End::Deadline));
assert_eq!(process(&[1, u64::MAX], u64::MAX), (1, End::ClockOverflow));
}
A modell nem állítja, hogy pontosan a határidőben megáll. Éppen azt mutatja meg, miért számít a munkadarab mérete. Az utolsó darab után is vizsgáljuk a határt, ezért egy teljes adatsort feldolgozó futás sem kap automatikusan időben teljesült minősítést. A completed azt számolja, ami a modell szerint már elvégzett munka, nem azt, amit szükség esetén vissza tudnánk vonni.
3. feladat: állj meg a következő együttműködési ponton
Készíts olyan feldolgozót, amely minden elem előtt meghív egy megszakítást jelző függvényt. A tesztben a második index elérésekor kérj leállást. Az első két eredmény maradjon látható, további ne keletkezzen. A Cell itt kizárólag egy szálon működő tesztállapot, nem szálak közötti megszakítási protokoll.
Megoldás
use std::cell::Cell;
fn collect_until(n: usize, mut cancelled: impl FnMut(usize) -> bool) -> Vec<usize> {
let mut completed = Vec::new();
for index in 0..n {
if cancelled(index) { break; }
completed.push(index);
}
completed
}
fn main() {
let requested = Cell::new(false);
let result = collect_until(5, |index| {
if index == 2 { requested.set(true); }
requested.get()
});
assert_eq!(result, vec![0, 1]);
assert!(collect_until(5, |_| true).is_empty());
assert_eq!(collect_until(3, |_| false), vec![0, 1, 2]);
}
A már elkészült elemeket nem töröljük azért, mert később megszakítás érkezett. Valódi mellékhatásnál külön kell eldönteni, mi számít befejezettnek, mi folytatható és mi igényel kompenzációt. A több szál közötti jelzéshez megfelelő szinkronizáció és tulajdonosi megállapodás kellene; a helyi Cell használatát erre nem lehet változatlanul átmásolni.
Hol legyen az utolsó ellenőrzés?
Egy visszafordíthatatlan művelet előtt érdemes újra megvizsgálni a feltételeket, de az ellenőrzés és a külső végrehajtás közötti rés ettől még létezhet. Az állapotgépnek ezért meg kell különböztetnie a még el nem indított és a már bizonytalan kimenetű munkát. A megszakítási kérés önmagában nem bizonyítja, hogy a külvilágban semmi nem történt.
A függvények a tesztben pontosan ismert időadatot kapnak. Egy valós alkalmazásban monoton időmegfigyelésre alkalmas órát kellene a kapuhoz kapcsolni, nem naptári dátumot összeadogatni. A mostani determinisztikus modell az egyenlőség, elsőbbség és részeredmény szerződését ellenőrzi, nem operációsrendszer-szintű határidő-garanciát. Ezért nincs sleep vagy időtartamra tett teljesítményállítás.
A megszakítás után megmaradó részhaladást a hívó felületnek is értelmeznie kell. A felhasználó számára más döntés következik a nulla elvégzett lépésből és a már befejezett első két elemből. A leállási ok önmagában kevés, ha az alkalmazás később folytatást vagy megismétlést kínál ugyanahhoz a munkához.
Elfogadási feltételek
- Pontosan a határidőn új munka nem indul, egyidejű megszakítás a rögzített elsőbbséget követi.
- A darabos feldolgozás megőrzi az elvégzett munka számát, és a túlfutást nem nevezi időben teljesült sikernek.
- A második indexnél kért megszakítás után az eredmény pontosan [0, 1], további elem nélkül.
Összefoglaló és továbblépés
A következő napló és visszajátszás leckében már azt is megnevezzük, milyen állapotból lehetne helyreállni. A megszakításra válaszoló memóriabeli ciklus önmagában nem tartós helyreállítás.