לדלג לתוכן

פתרון חקירה והגנת תכנון

המודל הבא שומר ניסיון פעיל עד להשלמה. הוא מיועד לתזמון ידני בthread יחיד; המימוש האסינכרוני מוסיף את מנגנון הסנכרון של הtransport. המפה מחזיקה את חיי הפעולה, והcompletion מקבל מזהה ניסיון שאינו משתנה.

#include <cassert>
#include <cstdint>
#include <map>
#include <stdexcept>
#include <utility>

class Operations {
    struct Operation {
        std::uint64_t request;
        bool client_waiting = true;
        bool cancel_requested = false;
    };
    std::map<std::uint64_t,Operation> active;
    std::uint64_t next = 1;
public:
    std::uint64_t start(std::uint64_t request) {
        if (active.size() >= 64) throw std::runtime_error("full");
        if (next == UINT64_MAX) throw std::overflow_error("attempt ids exhausted");
        auto attempt = next++;
        active.emplace(attempt,Operation{request});
        return attempt;
    }
    void timeout(std::uint64_t attempt) { active.at(attempt).client_waiting = false; }
    void cancel(std::uint64_t attempt) { active.at(attempt).cancel_requested = true; }
    std::pair<std::uint64_t,int> complete(std::uint64_t attempt, int result) {
        auto found = active.find(attempt);
        if (found == active.end()) throw std::logic_error("unknown or duplicate completion");
        auto answer = std::make_pair(found->second.request,result);
        active.erase(found);
        return answer;
    }
    std::size_t pending() const { return active.size(); }
};
int main() {
    Operations operations;
    auto old_attempt = operations.start(101);
    operations.timeout(old_attempt);
    operations.cancel(old_attempt);
    auto new_attempt = operations.start(102);
    assert(operations.pending() == 2);
    auto old_result = operations.complete(old_attempt,111);
    auto new_result = operations.complete(new_attempt,222);
    assert(old_result.first == 101 && old_result.second == 111);
    assert(new_result.first == 102 && new_result.second == 222);
    assert(operations.pending() == 0);
    bool duplicate = false;
    try { operations.complete(old_attempt,111); }
    catch (const std::logic_error&) { duplicate = true; }
    assert(duplicate);
}

המודל מחזיר את התוצאה גם כאשר הclient כבר הפסיק להמתין. במוצר מעבירים אותה למאגר התוצאות או למסלול discard מתועד לפי החוזה, ולא לUI של עבודה אחרת. הסימון cancel_requested אינו מכריח את התוצאה להיות canceled; ייתכן שהפעולה הספיקה להצליח לפני שהביטול הוכרע.

הבדיקה מכריחה את הסדר הבעייתי ומוכיחה הפרדה בין תוצאות. היא גם בודקת השלמה כפולה. היא אינה מוכיחה שהAPI של מערכת ההפעלה תמיד יקרא לcomplete פעם אחת; את זה צריך לקשור למסלולי immediate failure, pending וcompletion של אותו API.

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

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