לדלג לתוכן

פתרון קורוטינות מעל מערכת ההפעלה

אובייקט מקומי שנוצר בתוך גוף הקורוטינה נשאר חי לאורך ההשהיה. אם מגיעים לסוף הגוף, הוא נהרס כחלק מסיום התחום שלו. אם הבעלים הורס מסגרת מושהית שאין לה משתמש חיצוני, ההרס מנקה גם את האובייקטים החיים במסגרת. ההיתר במקרה הפשוט אינו עובר אוטומטית לפעולת I/O שהגישה כתובות לקרנל.

הוספת שמירת חריגה ל־promise יכולה להיראות כך; הקטע מחליף את הטיפול בחריגה בתוך promise_type של ההרצאה:

std::exception_ptr error;

void unhandled_exception() noexcept {
    error = std::current_exception();
}

פעולת בדיקת תוצאה בתוך Step צריכה לוודא שהמסגרת קיימת ושהקורוטינה הסתיימה לפני קריאת השגיאה:

void result() const {
    if (!handle_ || !handle_.done())
        throw std::logic_error("task is not complete");
    if (handle_.promise().error)
        std::rethrow_exception(handle_.promise().error);
}

הקטעים דורשים את הכותרת stdexcept בנוסף לכותרות ההרצאה. הם אינם מעבירים בעלות על המסגרת לחריגה. ה־Step ממשיך להחזיק אותה עד destruction, והקריאה ל־result רק מפרסמת את תוצאת הפעולה.

טבלת החיים המהותית

לפני רישום הבקשה, בעל ה־task יכול לבחור לא להתחיל אותה. אחרי כשל הגשה שאינו pending, אין פעולה של מערכת ההפעלה שמחזיקה את הכתובות, ולכן אפשר להמשיך מיד אל await_resume. אחרי הגשה שהתקבלה, המסגרת נשארת עד שה־dispatcher צרך completion.

בקשת ביטול אינה מעבר ל"משוחרר". היא מסמנת כוונה ונשארת בתוך המצב הפעיל עד השלמה. כאשר החבילה נצרכה, המנוע מסיר את הבקשה, כותב את התוצאה ומחדש את הקורוטינה. רק לאחר שהקורוטינה סיימה ואין פעולות פעילות שנשענות על המסגרת, הבעלים רשאי להרוס אותה.

מרוץ ההגשה

במנוע בעל thread יחיד, ה־dispatcher אינו פועל בזמן ש־await_suspend מגישה את הקריאה. חבילה יכולה כבר להימצא בתור הקרנל, אבל הקוד שצורך אותה לא ירוץ עד שנחזור ללולאה. לכן כתיבת continuation ורישום הבקשה מסתיימים לפני resume.

במנוע רב־תהליכוני ההנחה הזו אינה קיימת. נדרש פרוטוקול שמכריע מי אחראי לחדש כאשר completion מתחרה עם סיום ההגשה. המצב והבעלות חייבים למנוע גם חידוש כפול וגם הרס לפני שהצד המגיש סיים להשתמש בשדותיו. הפתרון אינו להשאיר pointer גולמי ולקוות שה־callback איטי יותר.

שימוש אחרי חידוש

אם הקורוטינה מסתיימת מיד אחרי co_await, משתנה ה־awaiter יכול לסיים את חייו בתוך resume. לכן dispatcher ששומר pointer אליו ומדפיס אחר כך שדה שלו עלול לגשת לאובייקט שכבר אינו חי. המנוע צריך להעתיק מראש את כל מה שנדרש לאחר החידוש, או פשוט לא לגעת בבקשה אחריו.

הפרויקט משתמש באפשרות השנייה: הסרת הרישום, כתיבת תוצאה, העתקת coroutine_handle וחידוש כפעולה האחרונה. זו מגבלה קלה לבדיקה שמצמצמת מאוד את מספר מקרי החיים שצריך להוכיח.