נפחים וטיפול בכשל¶
נניח שתוכנית רואה שיש עשרה גיגה־בתים פנויים ואז מנסה לכתוב קובץ קטן, ובכל זאת הכתיבה נכשלת. המספר שנמדד הוא תצפית בזמן מסוים, לא הזמנה של מקום. ייתכן שהמשתמש מוגבל במכסה, שכותב אחר צרך מקום או שהיעד השתנה או נעלם.
נפריד בין נתיב, volume והתקן פיזי. אות כונן אינה הבטחה לכונן פיזי אחד, ו־volume יכול להישען על אחסון וירטואלי, כמה התקנים או שירות מרוחק. כלי ניהול צריך לשאול את השכבה הרלוונטית בלי להמציא מבנה מתוך שם.
מידע על נפח זמין¶
התוכנית הבאה מקבלת נתיב קיים, למשל שורש כונן או תיקיית מעבדה, ומדפיסה מדדי מקום. היא אינה כותבת לאחסון:
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <iostream>
int wmain(int argc, wchar_t** argv) {
if (argc != 2) return 2;
ULARGE_INTEGER available{}, total{}, free_total{};
if (!GetDiskFreeSpaceExW(argv[1], &available, &total, &free_total)) {
std::cerr << "space: " << GetLastError() << '\n';
return 1;
}
std::cout << "available-to-caller=" << available.QuadPart
<< "\ntotal-visible=" << total.QuadPart
<< "\nfree-total=" << free_total.QuadPart << '\n';
wchar_t volume_root[32768]{};
if (!GetVolumePathNameW(argv[1], volume_root, 32768)) {
std::cerr << "volume path: " << GetLastError() << '\n';
return 1;
}
std::wcout << L"volume-root=" << volume_root << L'\n';
wchar_t volume_name[128]{};
if (GetVolumeNameForVolumeMountPointW(
volume_root, volume_name, 128))
std::wcout << L"volume-name=" << volume_name << L'\n';
else
std::cerr << "volume name unavailable: " << GetLastError() << '\n';
}
המספר הזמין לקורא יכול להיות שונה מן המקום הפנוי הכללי, למשל עקב מכסות. גם מידע הגודל מתפרש לפי חוזה הפונקציה והמערכת. שאילתת שם volume יכולה לא להתאים ליעד מרוחק או לספק מסוים, ולכן הכשל נשמר ככשל של אותה שאילתה ולא כהוכחה שאין אחסון.
ה־buffer הגדול בדוגמה הוא מגבלה מפורשת של כלי המעבדה. במוצר אפשר לבחור ניהול דינמי לפי חוזי ה־API, ולהימנע מהנחה שכל נתיב מתאים ל־MAX_PATH. אין צורך לפתוח PhysicalDrive כדי לדעת כמה מקום זמין לקובץ רגיל.
בדיקת מקום אינה עסקה¶
גם אם available גדול מן הקובץ המתוכנן, ההקצאה יכולה להיכשל מאוחר יותר. דחיסה, sparse ranges, metadata, מכסות ופעולות מקבילות משנים את היחס בין גודל לוגי לצריכת אחסון. בדיקת מקום יכולה לשפר הודעת משתמש, אבל מסלול הכתיבה עדיין צריך לטפל בכשל.
במיוחד אין לאשר עסקה אחרי כתיבת data אם commit או flush נכשלו. המערכת יכולה להיות במצב שבו חלק מן הבתים קיימים, ואסור לנסות להסתיר זאת באמצעות כתיבה מחדש ללא recovery. בפרויקט נשמור יומן עם פורמט שניתן לבדוק, ונגדיר היכן מותר להמשיך append.
ניסיון חוזר צריך לשמר משמעות¶
שגיאת sharing violation עשויה להיעלם כאשר תהליך אחר סוגר handle. שגיאת access denied יכולה לנבוע ממדיניות קבועה, ולא בהכרח תשתפר אחרי המתנה. נתיב שלא נמצא יכול להיות טעות קלט או תוצאה של התקן שנעלם. בחירת retry לפי כל שגיאה שאינה אפס יוצרת לולאה שמסתירה את האבחון.
נגדיר מספר ניסיונות, deadline ו־backoff מוגבל עם פיזור כאשר יש לקוחות רבים. נרשום את קוד השגיאה הראשון והאחרון ואת זהות היעד. אם כל ניסיון פותח מחדש אותו שם, ייתכן שבינתיים השם מצביע לאובייקט אחר. לכן פעולת retry צריכה לשמר את חוזה הזהות או להודיע שהיא מתחילה פעולה חדשה על יעד שנפתר מחדש.
גם כתיבה שנכשלה אינה תמיד "לא קרה דבר". עבור פעולה שכבר יכלה להתבצע חלקית, ניסיון חוזר צריך לדעת מאיזה offset ממשיכים ומה כבר אושר. עבור בקשה עסקית נשתמש במזהה בקשה וביומן, ולא נניח שאפשר פשוט לשלוח אותה שוב.
הזרקת כשל בלי לפגוע במחשב¶
כדי לבדוק disk full אין צורך למלא את כונן המשתמש. אפשר להשתמש במערכת קבצים מבודדת במעבדה או בממשק כתיבה מדומה שמחזיר כשל אחרי מספר בתים מוגדר. הראשון בודק את התנהגות מערכת ההפעלה בתצורה המסוימת; השני בודק את פרוטוקול היישום באופן שניתן לשחזור.
בדיקת access denied יכולה להתבצע מול קובץ מעבדה שנפתח מראש במצב שיתוף מתנגש, אך צריך לדווח שזהו sharing violation ולא לשנות את שם הבדיקה. אם רוצים לבדוק הרשאות ממש, משתמשים באובייקט מעבדה עם ACL מוגדר ומתעדים את השחזור. בפרק האבטחה נבנה את התשתית לכך.
היעלמות נתיב אפשר לבדוק באמצעות תהליך מעבדה ששומר handle, משנה שם של קובץ חדש ומנסה לפתוח שוב בשם הישן. אין צורך לנתק התקן אמיתי או לסכן קבצים קיימים. כל בדיקה צריכה להשאיר את תוצאותיה ואת גבולותיה ברורים.
קריאה להעמקה¶
התיעוד של מקום פנוי הזמין לקורא ושל נתיב volume מגדיר את השאילתות. החוזה של retry ושל פרסום תוצאה הוא החלטה של היישום.