לדלג לתוכן

פתרון עצירה והתאוששות של שירות

פתרון התרגיל הראשון

נשתמש באותה נעילה לבדיקת הקבלה ולהכנסה לתור. הקוד הבא הוא תוכנית console מלאה שאפשר לבדוק לפני שילוב בServiceMain. השימוש בassert מתאים לבדיקה הזאת; אין כאן בדיקת קלט של לקוח שתיעלם בrelease.

#include <cassert>
#include <condition_variable>
#include <deque>
#include <mutex>
#include <optional>

enum class result { accepted, full, stopping };

class queue {
    std::mutex lock_;
    std::condition_variable changed_;
    std::deque<int> jobs_;
    bool accepting_ = true;
public:
    result submit(int job) {
        std::lock_guard guard(lock_);
        if (!accepting_) return result::stopping;
        if (jobs_.size() == 4) return result::full;
        jobs_.push_back(job);
        changed_.notify_one();
        return result::accepted;
    }
    void begin_stop() {
        std::lock_guard guard(lock_);
        accepting_ = false;
        changed_.notify_all();
    }
    std::optional<int> take() {
        std::unique_lock guard(lock_);
        changed_.wait(guard, [&] { return !jobs_.empty() || !accepting_; });
        if (jobs_.empty()) return std::nullopt;
        int job = jobs_.front();
        jobs_.pop_front();
        return job;
    }
};

int main() {
    queue jobs;
    auto first = jobs.submit(10);
    auto second = jobs.submit(20);
    jobs.begin_stop();
    auto third = jobs.submit(30);
    assert(first == result::accepted);
    assert(second == result::accepted);
    assert(third == result::stopping);
    assert(jobs.take() == 10);
    assert(jobs.take() == 20);
    assert(!jobs.take());
}

נקודת ההכרעה נמצאת בתוך הקטע המוגן. אם submit ראה קבלה פתוחה והכניס בהצלחה, העצירה שתגיע אחריו תראה בקשה שנמצאת בתור. אם begin_stop שינה קודם את המצב, ההכנסה תידחה. אם ההקצאה בתוך push_back נכשלת, לא החזרנו accepted; בשילוב השירות נתרגם את החריגה לשגיאת בקשה ולא ניתן לה לצאת דרך callback של ווינדוס.

התור ריק אינו אומר שכל העבודה הסתיימה. הworker יכול להחזיק בקשה שכבר הוציא. בשירות נמתין גם לסיום הworkerים ולכל I/O שבבעלותם לפני דיווח stopped. זאת הסיבה שלא הוספנו כאן פונקציה בשם all_finished שמסתכלת רק על גודל התור.

פתרון התרגיל השני

הtimer מספק ראיה שהtimer חי, לא שהrecovery מתקדם. נחלק את השחזור ליחידות שמייצרות מדד אמיתי, למשל מספר records שאומתו והוחלו. אחרי יחידה נפרסם checkpoint חדש ונעדכן אומדן בהתאם לעבודה שנותרה.

אם פעולה אחת יכולה להיתקע בלי נקודת התקדמות, נגדיר לה deadline ומסלול כשל. לא נעקוף את הבעיה עם מונה שרץ במקביל. את המידע המפורט נרשום בלוג, ואילו לSCM נדווח מצב שמתאים למה שאנחנו באמת יודעים.

פתרון התרגיל השלישי

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

אם שני הדברים אינם אפשריים, חייבים לחשוף מצב לא מוכרע או לבחור חוזה חלש יותר. הבטחה של exactly once רק מפני שהSCM מפעיל אותנו מחדש אינה ניתנת להצדקה.

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