8.4 - מוכנות מתארים וזהות אירועים - פתרון
פתרון המוכנות¶
פתרון ראשון - ריק זמנית מול סוף הזרם¶
הקטע הבא מחליף את סוף הפונקציה experiment, אחרי ההדפסה והבדיקה של מספר האירועים. הוא משתמש במשתנים שנוצרו באותה פונקציה.
let mut collected = part.to_vec();
loop {
let mut buffer = [0_u8; 8];
let count = unsafe {
libc::read(reader.as_raw_fd(), buffer.as_mut_ptr().cast(), buffer.len())
};
if count > 0 {
collected.extend_from_slice(&buffer[..count as usize]);
continue;
}
if count == 0 {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "writer still alive"));
}
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::Interrupted { continue; }
if error.kind() == io::ErrorKind::WouldBlock { break; }
return Err(error);
}
assert_eq!(&collected, b"abcdef");
drop(writer);
let mut byte = [0_u8; 1];
loop {
let count = unsafe { libc::read(reader.as_raw_fd(), byte.as_mut_ptr().cast(), 1) };
if count == 0 { break; }
if count < 0 {
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::Interrupted { continue; }
return Err(error);
}
panic!("unexpected remaining byte");
}
Ok(())
השגיאה WouldBlock אומרת שאין כרגע נתונים שאפשר לקרוא בלי לחסום, כאשר עדיין יכול להופיע כותב עם נתונים נוספים. החזרה של אפס במקרה הזה מציינת סוף זרם לאחר סגירת כל קצות הכתיבה. ערבוב שני המצבים יכול לגרום לסגירה מוקדמת של חיבור חי או להמתנה אינסופית לכותב שכבר אינו קיים.
פתרון שני - דחיית אירוע ישן¶
התוכנית הבאה מדגימה את בדיקת הזהות בלי להסתמך על תזמון של שימוש חוזר במתאר אמיתי.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Token { slot: u32, generation: u32 }
struct Entry { generation: u32, active: bool }
fn accepts(entries: &[Entry], token: Token) -> bool {
entries.get(token.slot as usize)
.is_some_and(|entry| entry.active && entry.generation == token.generation)
}
fn recycle(entry: &mut Entry) -> Result<(), &'static str> {
entry.active = false;
entry.generation = entry.generation.checked_add(1).ok_or("retired slot")?;
entry.active = true;
Ok(())
}
fn main() {
let mut entries = [Entry { generation: 8, active: true }];
let old = Token { slot: 0, generation: 8 };
assert!(accepts(&entries, old));
recycle(&mut entries[0]).unwrap();
assert!(!accepts(&entries, old));
assert!(accepts(&entries, Token { slot: 0, generation: 9 }));
entries[0].generation = u32::MAX;
assert!(recycle(&mut entries[0]).is_err());
assert!(!entries[0].active);
}
הבדיקה האחרונה משאירה את המשבצת לא פעילה במקרה של גלישה. כך אין מעבר שקט בחזרה לדור אפס. במערכת מלאה צריך לשמור גם את סיבת הפרישה כדי שמנגנון הקצאה אחר לא יפעיל את המשבצת שוב בטעות.
פתרון שלישי - מוכנות פנימית¶
כאשר התקציב נגמר לפני ריקון, המנוע מוסיף את החיבור לתור מוכן מקומי ומאפשר לחיבורים אחרים לקבל סבב. הוא חוזר להמתנה חוסמת רק כאשר אין עבודה מקומית שידוע שאפשר לנסות. כאשר מתקבל EAGAIN, אפשר להסיר את החיבור מתור המוכנות המקומי ולסמוך שוב על מנגנון האירועים המתאים.
מחיקת החיבור מכל התורים בזמן שנותרו בו בתים יכולה להקפיא אותו עד שיגיע שינוי חיצוני נוסף. התסמין הוא חיבור שיש בו נתונים אבל אינו מתקדם, בעוד שזמן המעבד של התהליך נמוך. הגדלת גודל הבאפר רק משנה כמה קשה לשחזר את התקלה; היא אינה מתקנת את הפרוטוקול.