8.5 - מכונת קלט ופלט עם זמן וביטול - פתרון
פתרון הזמן והביטול¶
פתרון ראשון - מספר תפוגות מצטבר¶
במפרט השעון יש לשנות גם את it_interval לעשר אלפיות השנייה וגם את it_value לאותו משך. לפני הכניסה ללולאה אפשר להוסיף thread::sleep(Duration::from_millis(50)). הקריאה תחזיר מספר תפוגות חיובי, ובדרך כלל מספר גדול מאחד. אין להצמיד את הבדיקה למספר חמש, משום שזמן השינה הוא גבול תחתון מעשי והמתזמן יכול להחזיר את התהליכון מאוחר יותר.
הניסוי בודק שמנגנון הקריאה מדווח על הצטברות, ולא שכל אירוע תזמון במערכת התרחש במועד אידיאלי. אם צריך בדיקה דטרמיניסטית של מדיניות הדבקת עבודה, מזינים אליה מונה ידוע באמצעות שעון מדומה ובודקים אותה בנפרד מהשעון הקרנלי.
פתרון שני - צריכת אות במיקום נשלט¶
הקוד הבא הוא תוכנית לינוקס עצמאית עם התלות libc. היא מסתיימת אחרי צריכת האות ולכן אינה משחזרת מסכה לקראת המשך פעילות אחרת.
use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
fn main() -> io::Result<()> {
let mut mask = std::mem::MaybeUninit::<libc::sigset_t>::uninit();
if unsafe { libc::sigemptyset(mask.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
let mut mask = unsafe { mask.assume_init() };
if unsafe { libc::sigaddset(&mut mask, libc::SIGUSR1) } != 0 {
return Err(io::Error::last_os_error());
}
let error = unsafe { libc::pthread_sigmask(libc::SIG_BLOCK, &mask, std::ptr::null_mut()) };
if error != 0 { return Err(io::Error::from_raw_os_error(error)); }
let raw = unsafe { libc::signalfd(-1, &mask, libc::SFD_CLOEXEC) };
if raw < 0 { return Err(io::Error::last_os_error()); }
let signal = unsafe { OwnedFd::from_raw_fd(raw) };
if unsafe { libc::kill(libc::getpid(), libc::SIGUSR1) } != 0 {
return Err(io::Error::last_os_error());
}
let mut information = std::mem::MaybeUninit::<libc::signalfd_siginfo>::uninit();
loop {
let count = unsafe { libc::read(signal.as_raw_fd(), information.as_mut_ptr().cast(),
std::mem::size_of::<libc::signalfd_siginfo>()) };
if count < 0 {
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::Interrupted { continue; }
return Err(error);
}
assert_eq!(count as usize, std::mem::size_of::<libc::signalfd_siginfo>());
break;
}
let information = unsafe { information.assume_init() };
assert_eq!(information.ssi_signo, libc::SIGUSR1 as u32);
println!("signal consumed through descriptor");
Ok(())
}
הקריאה pthread_sigmask מחזירה מספר שגיאה ישירות, ולכן אין לקרוא עבורה את errno כאילו הייתה קריאת מערכת רגילה. ההנחה על חסימה של כל התהליכונים תקפה כאן משום שאין תהליכונים אחרים. ספרייה כללית אינה רשאית להניח שהיא יכולה לשנות לבדה את מדיניות האותות של היישום; עליה לקבל חוזה מפורש מהיישום או להשתמש במנגנון בקרה אחר.
פתרון שלישי - היסט שנשמר בביטול¶
התוכנית הבאה מדמה את ההתקדמות בלי להזדקק לתזמון רשת.
enum Step { Written(usize), WouldBlock }
fn main() {
let buffer = b"abcdefgh";
let steps = [Step::Written(3), Step::WouldBlock, Step::Written(2), Step::Written(3)];
let mut offset = 0;
let mut observed = Vec::new();
for step in steps {
match step {
Step::Written(count) => {
assert!(count <= buffer.len() - offset);
observed.extend_from_slice(&buffer[offset..offset + count]);
offset += count;
}
Step::WouldBlock => assert_eq!(offset, 3),
}
}
assert_eq!(offset, buffer.len());
assert_eq!(&observed, buffer);
}
ביטול אחרי ההתקדמות הראשונה צריך להחזיר לפחות מידע כגון Cancelled { accepted_bytes: 3 }, או להסתיר את המספר מאחורי חוזה שמגדיר במפורש תוצאה לא ידועה וסגירת חיבור. המספר מציין בתים שהכתיבה המקומית קיבלה, ואינו מוכיח שהיישום המרוחק כבר עיבד אותם. ההבחנה הזאת תהיה חיונית כאשר נדון באישור עסקאות בפרוטוקול רשת.