8.9 - השלמות קלט ופלט ובעלות בבאפר - פתרון
פתרון מעקב ההשלמות¶
פתרון ראשון - זהות בלי סדר¶
התוכנית הבאה משתמשת באותה תלות של ניסוי הפעולה הריקה. גודל התור מספיק לכל ארבע ההגשות, ואין פעולות שמחזיקות באפר חיצוני.
use io_uring::{opcode, IoUring};
use std::collections::BTreeSet;
use std::io;
fn main() -> io::Result<()> {
let mut ring = IoUring::new(8)?;
for identity in 1..=4 {
let entry = opcode::Nop::new().build().user_data(identity);
unsafe {
ring.submission().push(&entry)
.map_err(|_| io::Error::other("queue full"))?;
}
}
let mut seen = BTreeSet::new();
while seen.len() != 4 {
for completion in ring.completion() {
let identity = completion.user_data();
assert!((1..=4).contains(&identity));
assert!(seen.insert(identity));
assert_eq!(completion.result(), 0);
}
if seen.len() == 4 { break; }
match ring.submit_and_wait(1) {
Ok(_) => {}
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
Err(error) => return Err(error),
}
}
assert_eq!(seen, BTreeSet::from([1, 2, 3, 4]));
Ok(())
}
הקבוצה בודקת כיסוי וייחודיות. היא אינה מבלבלת בין סדר הביצוע, סדר פרסום ההשלמות וסדר העיבוד של התוכנית. עבור תלות בין פעולות צריך להשתמש בחוזה מפורש של הממשק או להגיש פעולה רק אחרי שהקוד קיבל את התנאי הדרוש.
פתרון שני - ביטול אינו שחרור¶
המודל הבא מייצג פעולה רגילה שמחייבת השלמה סופית אחת. הוא אינו מנסה לייצג פעולות מרובות השלמות או דגלים שמדכאים השלמות.
struct Operation {
buffer: Option<Vec<u8>>,
cancellation_seen: bool,
original_seen: bool,
}
impl Operation {
fn new() -> Self {
Self { buffer: Some(vec![0; 64]), cancellation_seen: false, original_seen: false }
}
fn cancelled_request_completed(&mut self) {
self.cancellation_seen = true;
}
fn original_completed(&mut self) {
assert!(!self.original_seen);
self.original_seen = true;
self.buffer.take();
}
}
fn main() {
let mut first = Operation::new();
first.cancelled_request_completed();
assert!(first.buffer.is_some());
first.original_completed();
assert!(first.buffer.is_none());
let mut second = Operation::new();
second.original_completed();
second.cancelled_request_completed();
assert!(second.buffer.is_none());
}
במימוש אמיתי נשמור גם את תוצאות שני האירועים ואת זהות הפעולה. ייתכן שצריך להשאיר רשומת מטא־נתונים גם אחרי שחרור הבאפר, כדי להתאים אליה אישור ביטול מאוחר. שחרור הנתונים וסיום חיי הזהות אינם חייבים להיות אותו מעבר.
פתרון שלישי - כתובת של הקצאה נפרדת¶
הווקטור מחזיק מצביע, אורך וקיבולת. הגדלה מעבר לקיבולת יכולה להקצות מקום חדש ולהעביר אליו את האיברים, גם אם מעטפת הווקטור עצמה נשארת באותה כתובת. לכן קיבוע המעטפת אינו הוכחה שהבאפר שאליו הקרנל ניגש נשאר תקף.
ממשק שלוקח את הבאפר בבעלות ומחזיר אותו רק אחרי השלמה מונע מהמתקשר להפעיל עליו הגדלה בזמן הביניים. אפשר גם להשתמש במאגר בעל חוזה כתובת יציבה ולתת לכל פעולה בעלות זמנית על משבצת. בכל פתרון צריך לבדוק את מסלול הביטול ואת מסלול השגיאה, משום שדווקא בהם קל להחזיר את הבאפר מוקדם מדי.