יישומים שנשברים בחזרה משינה — איך אירועי צריכת החשמל של Windows עובדים ואיך לבנות יישומים עסקיים ששורדים אותם

· · Windows, ניהול צריכת חשמל, פיתוח Windows, יישומים עסקיים, בקרת התקנים, חקירת תקלות, Win32 API

“סגרתי את המחשב הנייד, פתחתי אותו בבוקר למחרת, והיישום העסקי היה מלא שגיאות.” “יישום הניטור של הציוד מאבד נתונים רק אחרי הצהריים.” “הכלי התושב שמייצא ל-Excel לפעמים נעצר בשגיאת חיבור.” — לכרטיסים האלה יש חשוד אחד משותף. שינה.

יישומים עסקיים מתקופת המחשבים השולחניים נכתבו על ההנחה הסמויה ש”המחשב נשאר דולק”. שדה הקרב העיקרי היום הוא המחשב הנייד, וכברירת מחדל הוא נרדם אחרי כמה דקות של חוסר פעילות. במחשב עם Modern Standby, סמנטיקת השינה עצמה השתנתה מהמודל המסורתי. מיועד למפתחות ולמפתחים שכותבים יישומים עסקיים ותוכנות בקרת ציוד ב-Windows, המאמר הזה מסדר ממקורות ראשוניים מה מערכת ההפעלה מודיעה ליישום לפני שינה ואחריה, מה נשבר, ואיך לכתוב יישום ששורד חזרה.

1. השורה התחתונה קודם

  • שינה היא אירוע שליישום “אין זכות לסרב” לו. מקבלים הודעה ממש לפני עם WM_POWERBROADCAST (PBT_APMSUSPEND), אבל תקופת החסד היא כשתי שניות, ובהשהיית חירום ההודעה אפילו לא מגיעה.12
  • בחזרה מהשהיה מגיע PBT_APMRESUMEAUTOMATIC, ובחזרה ביוזמת משתמש מגיע גם PBT_APMRESUMESUSPEND. עבודה הכרחית כמו חיבור מחדש שייכת ככלל לראשון. כניסות ויציאות ממצב הסרק הנמוך של Modern Standby לא תמיד מתיישרות עם ההודעות האלה, לכן מתייחסים להודעות כעזר.34
  • מתכננים בהנחה שחיבורי TCP, פורטים טוריים ו-handles של התקנים לא שורדים מעבר לחזרה. לוגיקת חיבור מחדש שבונה אותם בהודעת חזרה או בשגיאת תקשורת היא האירוע המרכזי.
  • שימו לב לטיימרים ולטיפול בזמן. עבודה מחזורית נעצרת בשינה, ואיך היא נורה מיד אחרי חזרה תלוי ב-API הטיימר ובסביבת הריצה. גם “קפיצה ענקית בזמן שחלף” קורית, לכן הגישה הבטוחה היא לבנות מחדש את לוח הזמנים בחזרה.
  • מדכאים שינה במפורש במרווחים שלא רוצים שישנו בהם. משתמשים ב-SetThreadExecutionState (ES_SYSTEM_REQUIRED) או בבקשת צריכת חשמל (PowerSetRequest), ומבטלים תמיד כשהעבודה מסתיימת.45
  • במחשב Modern Standby המערכת עדיין רצה לסירוגין בשינה, אבל יישומי שולחן העבודה מושהים. אי אפשר להחזיק את הציפייה ש”היישום שלנו אמור להמשיך לרוץ בשינה”.6
  • כלי החקירה הסטנדרטיים הם powercfg ‏(/requests, /lastwake, /sleepstudy) ו-Kernel-Power ביומן האירועים.

2. מה קורה סביב שינה — זרימת אירועי צריכת החשמל

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

אירוע משמעות
PBT_APMSUSPEND עומד להיכנס לשינה (ההזדמנות האחרונה להתכונן)
PBT_APMRESUMEAUTOMATIC חזר (תמיד מגיע בחזרה)
PBT_APMRESUMESUSPEND חזרה שנגרמה מפעולת משתמש (זה מותנה)

PBT_APMSUSPEND הוא ההודעה ממש לפני שינה, וכאן אפשר להתכונן בסגירת קבצים ושמירת מצב. יש שני תנאים. ראשית, הזמן המותר לעיבוד הוא כשתי שניות לכל יישום, ואם חורגים המערכת ממשיכה בלי לחכות.1 שנית, בהשהיית חירום כמו סוללה קריטית, הוא נרדם מיד בלי הודעה מוקדמת.2 תכנון ש”חייב להסתיים לפני השינה” לא מחזיק. מתייחסים להודעה כהזדמנות “לעשות אם מספיקים”, ואת העבודה העיקרית שמים בצד החזרה.

צד החזרה הוא שני שלבים. PBT_APMRESUMEAUTOMATIC מגיע בחזרה ממעבר השהיה. מעליו, אם המחשב חזר בגלל פעולת משתמש כמו כפתור ההפעלה או הקשה (או שנכחה משתמש זוהתה אחר כך), מגיע PBT_APMRESUMESUSPEND. לעומת זאת, חזרה ללא השגחה להשכמה מרחוק ברשת או לתחזוקה מעבירה רק PBT_APMRESUMEAUTOMATIC.3 שני השלבים האלה עצמם הם רמז לפיצול העבודה — עושים התאוששות מכנית כמו בניית חיבורים מחדש ב-PBT_APMRESUMEAUTOMATIC, ופעולות שפונות למשתמש כמו עדכון מסך או בקשת התחברות מחדש ב-PBT_APMRESUMESUSPEND.

זרימת ההודעות לשינה ולחזרהPBT_APMSUSPEND מגיע ממש לפני שינה עם כשתי שניות חסד; בחזרה PBT_APMRESUMEAUTOMATIC תמיד מגיע, ו-PBT_APMRESUMESUSPEND בא אחריו רק בחזרה ביוזמת משתמשיישוםOSיישוםOSשינה (הקוד לא רץ)PBT_APMSUSPEND (כשתי שניות חסד)שמירת מצב וסגירת חיבוריםPBT_APMRESUMEAUTOMATIC (מגיע בחזרה)חיבור מחדש ושחזור מצבPBT_APMRESUMESUSPEND (רק בחזרת משתמש)עדכון מסך ועבודה שפונה למשתמש

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

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

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

שימו לב ש-WM_POWERBROADCAST לא מבדיל את סוג מצב צריכת החשמל הנמוכה (שינה מול היברנציה).4 ההפשטה הנכונה ליישום היא לטפל בזה כסוג אחד של אירוע: “עצר, וחזר”. שירותים בלי חלון ויישומי קונסול יכולים לקבל את אותן הודעות עם RegisterSuspendResumeNotification בצורת callback ‏(DEVICE_NOTIFY_CALLBACK).7

פיצול העבודה בין שני שלבי החזרהשמים התאוששות מכנית כמו חיבור מחדש ב-PBT_APMRESUMEAUTOMATIC שמגיע בחזרה; שמים עבודה שפונה למשתמש כמו עדכון מסך או בקשת התחברות מחדש ב-PBT_APMRESUMESUSPEND שמגיע רק בחזרה ביוזמת משתמשPBT_APMRESUMEAUTOMATIC (בחזרה)התאוששות מכניתPBT_APMRESUMESUSPEND (חזרת משתמש)עבודה שפונה למשתמשחיבור מחדש ופתיחה מחדש של handlesעדכון מסך ובקשת התחברות מחדש

איור 3: האחרון לא מגיע בחזרה ללא השגחה, ולכן שמים התאוששות הכרחית עליו יחמיצו אותה.

3. Modern Standby — משמעות ה”שינה” השתנתה

עובדה מודרנית נוספת שצריך לקבל היא Modern Standby. שינת S3 המסורתית הייתה מודל פשוט ש”עצר את המערכת כולה”; שינה במחשב Modern Standby היא מודל דמוי סמארטפון שבו המערכת ממשיכה לרוץ לסירוגין אחרי שהמסך כבה.

מה שחשוב ליישום עסקי כאן הוא שיישומי שולחן העבודה מושהים על ידי Desktop Activity Moderator ‏(DAM) בשלב הראשון של הכניסה לשינה.6 המערכת עצמה עדיין רצה מדי פעם כדי לשמור את הרשת ולקבל הודעות, אבל הרכיבים שמרוויחים מזה הם אלה שמשתתפים במנגנון הזה — קוד יישום שולחן עבודה רגיל לא רץ. לכן מנקודת מבט של מפתח המסקנה זהה ל-Modern Standby ול-S3 — מתכננים בהנחה שהקוד שלכם לא רץ בשינה.

ההבדל בין שינה מסורתית ל-Modern Standbyשינת S3 מסורתית עוצרת את המערכת כולה, בעוד שב-Modern Standby המערכת עדיין רצה לסירוגין אחרי שהמסך כבה. יישומי שולחן העבודה מושהים על ידי DAM בשני המקרים, ולכן קוד היישום לא רץשינת S3 מסורתית: כל המערכת עוצרתקוד היישום לא רץModern Standby: המערכת רצה לסירוגיןיישומי שולחן העבודה מושהים על ידי DAM

איור 4: המודל השתנה, אבל ליישום שולחן עבודה המסקנה זהה: “אי אפשר לרוץ בשינה”.

זהירות נוספת היא כמה מעט אפשר לסמוך על ההודעות. ב-Modern Standby, כניסות ויציאות מסרק בצריכת חשמל נמוכה לא מתיישרות עם מעבר ההשהיה המסורתי, וחיבור כבר יכול להיות שבור בלי שהודעה תגיע בכלל. מתייחסים להודעת החזרה כעזר, ושמים חיבור מחדש שמופעל מזיהוי שגיאה (פרק 5) בנתיב ההתאוששות הראשי.

הבדל נוסף הוא התחושה ה”חלקלקה” של ההתנהגות. ההגעה לעומקי השינה היא בשלבים, ותזמון הניתוקים והעצירות אינו חד כמו ב-S3. גם ההבחנה בין “המסך רק כבה” לבין “הוא נרדם” קשה למשתמש לראות, ולכן כשלוקחים סימפטום צריך לאשר “האם סגרו את המכסה” ו”כמה דקות נשאר ללא פעילות”.

4. מה נשבר — סימפטומים קלאסיים

חיבור ה-TCP מת. בשינה, הצד השני, NAT וחומות אש מתייחסים לשתיקה שלכם כפקיעת זמן וזורקים את החיבור. גרוע מזה, הסוקט בצד הזה לא יודע על השגיאה, ולכן הוא נכשל רק כששולחים או מקבלים אחרי חזרה. או גרוע עוד יותר, המתנת קבלה לא נכשלת בכלל (לכן צריך keepalive). לחיבורי מסד נתונים ול-WebSockets יש אותה צורה.

Handles של פורט טורי והתקן USB הופכים ללא-תקפים. התקן מחובר ב-USB יכול להיראות, בחזרה, כאילו “נותק וחובר שוב” פעם אחת, וה-handle שהיה פתוח מתחיל להחזיר שגיאות. זה הדפוס הטיפוסי של יישום בקרת ציוד ש”מקבל שגיאת תקשורת רק אחרי הצהריים”. תכנון חיבור מחדש מכוסה גם במאמר התקשורת הטורית.

רציפות הזמן נשברת. עבודה מונעת-טיימר כמו “סקר כל 10 שניות” לא נורה בשינה. איך היא נורה מיד אחרי חזרה (עבודה שפגה נורה פעם אחת מיד, שום דבר לא קורה עד המחזור הבא, וכן הלאה) תלוי ב-API הטיימר ובסביבת הריצה, לכן אל תשארו את הטיפול בטיקים שהוחמצו להתנהגות מרומזת — הגישה הבטוחה היא לבנות מחדש את לוח הזמנים בהודעת החזרה. גם חישובי זמן שחלף (ההפרש מהחותמת הקודמת) הופכים פתאום ל”שווי 8 שעות”, וחישובי ממוצע או שיפוטי פקיעת זמן נשברים. עבודה מתוזמנת כמו “לרוץ כל לילה ב-02:00” פשוט לא רצה אם המחשב ישן באותה שעה (מעירים עם תכונת ההשכמה משינה של Task Scheduler אם צריך).

שלוש צורות שבהן רציפות הזמן נשברתעבודה מחזורית נעצרת בשינה והירי אחרי חזרה שונה לפי API, לכן בונים מחדש את לוח הזמנים בחזרה; ההפרש מהחותמת הקודמת הופך ענק אחרי חזרה, לכן שומרים עליו; עבודה מתוזמנת לא רצה אם המחשב ישן, לכן שוקלים השכמה משינה של Task Schedulerעבודה מחזורית: נעצרתבנייה מחדש של לוח הזמניםזמן שחלף: מתנפחשמירה מפני הפרשים חריגיםמתוזמן: לא רץהשכמה משינה

איור 5: כותבים טיפול בטיימר ובזמן בהנחה ש”הזמן קופץ”. לכל אחת משלוש הצורות יש סוג נגד-מידה.

שלושה דברים שנשברים מעבר לשינהמעבר לשינה, חיבור TCP נזרק בפקיעת זמן בצד השני, handle של התקן USB מתבטל כחיבור מחדש, ועבודה מבוססת-זמן-שחלף רואה קפיצת זמן ענקית. מתאוששים בכל אחד עם חיבור מחדש, פתיחה מחדש ושמירת הפרשמרווח השינהTCP: העמית זרק אותוUSB או זמן שחלף?USB: handle לא תקףזמן שחלף: קפיצהזיהוי + חיבור מחדשפתיחה מחדש של ההתקןשמירה מפני הפרשים חריגים

איור 6: מה שנשבר נופל לשלוש משפחות — “חיבורים”, “handles” ו”רציפות הזמן” — ולכל אחת יש סוג התאוששות מיושב.

אימות מחדש למשאבים משותפים. כונני רשת ו-VPN לעיתים קרובות צריכים להיווצר מחדש אחרי חזרה, ויש “עמק הפעלה” של כמה עד כמה עשרות שניות מיד אחרי חזרה שבו גישה נכשלת. בטוח יותר לא לנסות הכול בבת אחת מיד אחרי חזרה, אלא לחכות קצת ולנסות בשלבים.

5. בניית יישומים ששורדים חזרה

העיקרון הוא דבר אחד. מניחים ש”חיבורים ו-handles לא שורדים מעבר לשינה”, ומבנים את היישום כך שתמיד אפשר להתאושש.

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

// C#: funnel both the resume notification and communication errors into the same reconnect path
protected override void WndProc(ref Message m)
{
    const int WM_POWERBROADCAST = 0x0218;
    const int PBT_APMRESUMEAUTOMATIC = 0x0012;
    if (m.Msg == WM_POWERBROADCAST && (int)m.WParam == PBT_APMRESUMEAUTOMATIC)
    {
        _connectionManager.RequestReconnect();   // idempotent reconnect request
    }
    base.WndProc(ref m);
}

עושים את עבודת החיבור מחדש עצמה אידמפוטנטית (בטוחה לא משנה כמה פעמים קוראים לה), מנסים שוב בכשל עם exponential backoff, ובמצב יציב מזהים חיבור מת מוקדם עם keepalive — שמים את השלושה כמערכת, ותשרדו לא רק חזרה משינה אלא גם נפילת רשת קצרה או אתחול התקן.

תכנון חיבור מחדש עמיד לחזרההודעת החזרה, שגיאת תקשורת וכשל keepalive כולם נשפכים לאותה עבודת חיבור מחדש אידמפוטנטית, שמנסה שוב עם exponential backoff בכשלכןלאהודעת חזרה (PBT_APMRESUMEAUTOMATIC)עבודת חיבור מחדש אידמפוטנטיתזיהוי שגיאת תקשורתכשל keepaliveהצליח?חזרה לפעולה רגילהניסיון חוזר אחרי exponential backoff

איור 7: מרכזים חיבור מחדש לנתיב אידמפוטנטי אחד, ונכנסים לאותו כביש מהודעת החזרה, מזיהוי שגיאה או מה-keepalive.

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

מדכאים שינה במפורש במרווחים שלא רוצים שישנו בהם. במהלך עבודה שאסור שישנו באמצעה — העברת נתונים, תקשורת רציפה עם התקן וכן הלאה — אפשר להשאיר את המערכת ערה עם SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) (מוסיפים ES_DISPLAY_REQUIRED אם רוצים גם להשאיר את המסך דולק).45 שיטה מנומסת יותר היא ה-API של בקשות צריכת חשמל (PowerCreateRequest + PowerSetRequest), שיכולה לצרף מחרוזת סיבה, ו-powercfg /requests יציג אז “מי חוסם ולמה”.8 שימו לב שדיכוי דרך SetThreadExecutionState הוא לפי תהליכון, ומבטלים אותו מאותו תהליכון שהגדיר אותו. לעבודה שמחליפה תהליכונים, כמו async/await, משתמשים בצד בקשת צריכת החשמל, שמנוהל לפי handle. יש זהירויות. ראשית, מה שהן מדכאות הוא שינת סרק אוטומטית. הן לא יכולות לעצור פעולת משתמש מפורשת כמו סגירת המכסה או בחירת שינה מתפריט התחלה, ולכן אי אפשר לדלג על תכנון החיבור מחדש של הפרק הזה גם בזמן דיכוי. שנית, בסוללה במחשב Modern Standby, גם בקשות צריכת החשמל האלה נחתכות זמן מה אחרי שפקיעת זמן השינה חולפת. עבודה שאי אפשר להפריע לה חייבת להיות מובטחת בחשמל AC או בתפעול.8 שלישית, תמיד מבטלים כשהעבודה מסתיימת. ביטול שהוחמץ הופך לבאג חדש: “המחשב הזה, משום מה, לא נרדם”.

שני אמצעים לדיכוי שינהבין אם משתמשים ב-SetThreadExecutionState הנוח או ב-API של בקשת צריכת חשמל שיכול לצרף מחרוזת סיבה ונראה למנהל דרך powercfg, תמיד מבטלים כשהעבודה מסתיימתמרווח עבודה שאסור שישנו בוSetThreadExecutionStateבקשת צריכת חשמל (PowerSetRequest)נוח — רק דגליםעם סיבה — נראה ב-powercfgתמיד מבטלים כשהעבודה מסתיימת

איור 8: לשני האמצעים, “מבטלים כשמסיימים” הוא תנאי מוחלט. בקשת צריכת חשמל שיכולה להראות את הסיבה נדיבה יותר לתפעול.

שירותים ויישומים בלי חלון מקבלים הודעות callback עם RegisterSuspendResumeNotification ‏(DEVICE_NOTIFY_CALLBACK).7 אם פעולה רציפה היא דרישה אמיתית, הפתרון השורשי הוא לבדוק מחדש תכנון שמשאיר את העבודה תושבת במחשב לקוח שנרדם, ולהעביר אותה לצד השרת או למחשב שמופעל בלי שינה.

6. חקירה — powercfg ויומן האירועים

חקירה סביב צריכת חשמל נעזרת היטב בכלים שמגיעים עם מערכת ההפעלה.

  • לא נרדם: powercfg /requests מציג את התהליכים והמנהלים שהנפיקו בקשת צריכת חשמל. “היישום שכח לבטל SetThreadExecutionState” מופיע כאן גם.
  • מתעורר מעצמו: powercfg /lastwake מציג את סיבת ההשכמה האחרונה, ו-powercfg /waketimers מציג טיימרים ששמורים כרגע להעיר את המחשב.
  • איכות Modern Standby: powercfg /sleepstudy מייצר דוח צריכת חשמל ופעילות לכל מרווח שינה.9
  • אישור ציר הזמן: מקור Kernel-Power ביומן האירועים (System) שומר רשומות של כניסה לשינה ושל חזרה. התאמה מול יומן היישום מאפשרת לאשר באופן אובייקטיבי האם “הייתה חזרה ממש לפני השגיאה”.
מיפוי סימפטומי תקלות צריכת חשמל לפקודות חקירהלסימפטום לא-נרדם, מוצאים מי מחזיק בקשת צריכת חשמל עם powercfg /requests; לסימפטום מתעורר-מעצמו, מוצאים את סיבת ההשכמה עם /lastwake ו-/waketimers; לציר זמן, משתמשים ב-Kernel-Power ביומן האירועיםלא נרדםpowercfg /requestsמתעורר מעצמוpowercfg /lastwake ו-/waketimersרוצים לאשר את ציר הזמןKernel-Power ביומן האירועיםדיכוי שינה שנשכח מופיע גם

איור 9: סימפטומים ממופים לפקודות חקירה בשלוש משפחות. קודם מאשרים “האם זה עתה ישן”, ואז מפצלים.

בטיפול בכרטיסים, עצם השאלה הראשונה “האם המחשב ישן ממש לפני (האם סגרו את המכסה)” מאיצה מאוד את הבידוד.

7. סיכום

  • אי אפשר לסרב לשינה. ההודעה המוקדמת (PBT_APMSUSPEND) היא best-effort עם כשתי שניות חסד, והיא לא מגיעה בחירום. שמים את התכנון העיקרי בצד החזרה.
  • הודעות החזרה הן PBT_APMRESUMEAUTOMATIC (בחזרה מהשהיה) + PBT_APMRESUMESUSPEND (בפעולת משתמש). משאירים חיבור מחדש מונע-שגיאה בנתיב הראשי למקרה שההודעה לא מגיעה.
  • מניחים שחיבורים ו-handles לא שורדים מעבר לחזרה, ומממשים את הסט התלת-חלקי של חיבור מחדש אידמפוטנטי + exponential backoff + keepalive.
  • שמים שמירה מפני “הפרשים חריגים” על עבודה מבוססת-זמן-שחלף. מתכננים עבודה מתוזמנת בהנחה שהיא לא רצה בשינה.
  • במרווחים שאסור שישנו בהם, מדכאים שינה במפורש עם SetThreadExecutionState או בקשת צריכת חשמל, ותמיד מבטלים כשמסיימים.
  • החקירה היא powercfg ‏(/requests, /lastwake, /sleepstudy) ויומן האירועים Kernel-Power. בטיפול בכרטיסים, שואלים קודם “האם זה עתה ישן”.

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

מאמרים קשורים

תחומי ייעוץ קשורים

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

קישורים

  1. Microsoft Learn, PBT_APMSUSPEND event. על כך שזה האירוע שמגיע ממש לפני שהמחשב נכנס למצב השהיה; על כך שהיישום אמור לסיים את העבודה הנדרשת לשמירת נתונים; ועל כך שהמערכת מאפשרת כשתי שניות לטפל בהודעה זו, ויישום שממשיך מעבר לזה נתון להפרעה.  2

  2. Microsoft Learn, System Power Management Events. על שידור מוקדם של שינויי מצב הפעלה כמו שינה; על כך ש-PBT_APMSUSPEND מגיע לפני שינת סרק כדי שאפשר יהיה להתכונן בסגירת קבצים ושמירת נתונים; על כך שהשהיית חירום (סוללה קריטית וכדומה) לא נותנת הודעה מוקדמת; על כך שטיפול בהודעה זו מותר לכל היותר כשתי שניות ליישום ונחתך אחרי פקיעת הזמן; ועל כך שכל יישום מקבל הודעה בחזרה.  2 3

  3. Microsoft Learn, PBT_APMRESUMESUSPEND event. על כך שזה נשלח אחרי PBT_APMRESUMEAUTOMATIC בחזרה ביוזמת משתמש או כשקלט משתמש מזוהה אחר כך; על כך שנשלח רק PBT_APMRESUMEAUTOMATIC בחזרה מסיבה חיצונית כמו השכמה מרחוק; ועל כך שהיישום אמור לפתוח מחדש קבצים שנסגרו בשינה ולהתכונן לקלט משתמש.  2

  4. Microsoft Learn, WM_POWERBROADCAST message. על כך ש-PBT_APMRESUMEAUTOMATIC תמיד נשלח בחזרה, ו-PBT_APMRESUMESUSPEND נשלח גם בחזרה מקלט משתמש; על כך שההודעה לא מבדילה את סוג מצב צריכת החשמל הנמוכה; על כך שפרטי מעברי מצב צריכת חשמל נרשמים ביומן האירועים של המערכת; ועל קריאה ל-SetThreadExecutionState כדי למנוע כניסה למצב צריכת חשמל נמוכה.  2 3 4

  5. Microsoft Learn, SetThreadExecutionState function (winbase.h). על כך ש-ES_SYSTEM_REQUIRED ו-ES_DISPLAY_REQUIRED יכולים לדכא שינת סרק וכיבוי תצוגה; ועל הכרזת דיכוי רציף עם ES_CONTINUOUS וביטולו בקריאה ל-ES_CONTINUOUS לבדו כשמסיימים.  2

  6. Microsoft Learn, Prepare software for modern standby. על כך ש-Desktop Activity Moderator ‏(DAM) משהה יישומי שולחן עבודה בשלב הראשון של המעבר ל-Modern Standby; ועל כך שהמערכת אחר כך נעה בשלבים לשלב צריכת חשמל נמוכה ולשלב חוסן, ורק רכיבים מותרים רצים לסירוגין.  2

  7. Microsoft Learn, RegisterSuspendResumeNotification function (winuser.h). על כך שזה ה-API שרושם לקבלת הודעות השהיה/חזרה, ועל ציון DEVICE_NOTIFY_CALLBACK כדי שיישום או שירות בלי חלון יוכלו לקבל את ההודעה דרך callback בנוסף למסירה לחלון.  2

  8. Microsoft Learn, PowerSetRequest function (winbase.h). על היכולת להגדיר סוג בקשה כמו השארת מערכת או תצוגה ערות על אובייקט בקשת צריכת חשמל שנוצר עם PowerCreateRequest; על היכולת לצרף מחרוזת סיבה אבחנתית; ועל כך שבקשות צריכת חשמל פתוחות ניתנות למנייה עם powercfg /requests.  2

  9. Microsoft Learn, Modern standby SleepStudy. על כך שהדוח שמייצר powercfg /sleepstudy מאפשר לבדוק, לכל מרווח Modern Standby, צריכת חשמל, פעילות וסיבת ההשכמה (כפתור הפעלה, קלט משתמש, טיימר השכמה וכן הלאה). 

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

האם יישום יכול לדעת מראש על שינה ולסרב לה?
ב-Windows הנוכחי אפשר לקבל את ההודעה, אבל אי אפשר לסרב. ממש לפני שינה, הודעת WM_POWERBROADCAST מעבירה אירוע PBT_APMSUSPEND, וכאן אפשר להתכונן בסגירת קבצים ושמירת מצב, אבל הזמן המותר לעיבוד הוא כשתי שניות לכל יישום, ואם חורגים המערכת ממשיכה בלי לחכות. בהשהיה חירום כמו סוללה קריטית, ההודעה המוקדמת עצמה לא מגיעה. לכן תכנון ש"חייב להסתיים לפני השינה" לא מחזיק; צריך תכנון שיכול להתאושש בחזרה, מתי שלא תהיה החיתוך. לקטע עבודה שבאמת לא רוצים שישנו באמצעו, מדכאים שינה במפורש עם SetThreadExecutionState או בקשת צריכת חשמל (PowerSetRequest).
איך מזהים שהמחשב חזר משינה?
אם ליישום יש חלון, מטפלים ב-WM_POWERBROADCAST. בחזרה מהשהיה מגיע PBT_APMRESUMEAUTOMATIC, ואם החזרה נגרמה מפעולת משתמש (כפתור ההפעלה או הקשה), אחריו מגיע PBT_APMRESUMESUSPEND. חזרה ללא השגחה שחוזרת מיד לשינה מעבירה רק PBT_APMRESUMEAUTOMATIC, לכן הפיצול הבסיסי הוא לשים עבודה הכרחית כמו חיבור מחדש בצד PBT_APMRESUMEAUTOMATIC ועבודה שפונה למשתמש כמו עדכון מסך בצד PBT_APMRESUMESUSPEND. שירותים בלי חלון ויישומי קונסול יכולים לקבל את אותן הודעות דרך callback עם RegisterSuspendResumeNotification ו-DEVICE_NOTIFY_CALLBACK.
אפשר להשאיר את היישום רץ במהלך שינה?
ככלל, לא. בשינה עצירת ביצוע ה-CPU עצמה (במחשב Modern Standby יישומי שולחן העבודה מושהים על ידי Desktop Activity Moderator), וקוד היישום לא רץ. יש שתי בחירות. אחת היא לדכא שינה רק בזמן שיש עבודה בתהליך. ציון ES_SYSTEM_REQUIRED ב-SetThreadExecutionState, או הנפקת בקשת צריכת חשמל עם PowerCreateRequest/PowerSetRequest, ידכאו שינת סרק אוטומטית באותו מרווח (אפשר לאשר ב-powercfg /requests). זה עדיין לא יכול לעצור פעולת שינה מפורשת כמו סגירת המכסה, ולכן גם בזמן דיכוי צריך להיות מוכנים לחזרה. השנייה היא לקבל שינה ולתכנן "להדביק אחרי החזרה". לעבודה מתוזמנת כמו אצווה לילית אפשר גם להעיר את המחשב עם "Wake the computer to run this task" של Task Scheduler. עבודה שבאמת צריכה לרוץ ברציפות שייכת לשרת או לשירות שמוגדר לא לישון.
למה חיבורי TCP ופורטים טוריים מפסיקים לעבוד אחרי חזרה?
כי גם מתאמי רשת והתקני USB יורדים למצב צריכת חשמל נמוכה בשינה. חיבור ה-TCP כבר נזרק על ידי הצד השני או על ידי פקיעת NAT או חומת אש, ושליחה/קבלה אחרי חזרה מחזירה שגיאה (לעיתים קרובות שמים לב רק כשיש שגיאה). מתאמי USB-לטורי וכדומה מטופלים לפעמים כהסרה והכנסה מחדש של התקן בחזרה, וה-handle שהיה פתוח הופך ללא-תקף. בשני המקרים ההנחה הנכונה היא ש"handles וחיבורים לא שורדים מעבר לחזרה", והתשובה הנכונה היא לממש לוגיקת חיבור מחדש שבונה את החיבור מחדש בהודעת חזרה או בשגיאת תקשורת. שילוב keepalive תקופתי עם ניסיונות חוזרים ב-exponential backoff בכשל הוא הדפוס המקובל.
איך חוקרים שינה לא צפויה או חזרה לא צפויה?
פקודת powercfg היא הכלי הראשון. בכיוון "לא נרדם", powercfg /requests מציג אילו תהליכים ומנהלים הנפיקו בקשת צריכת חשמל שחוסמת שינה. בכיוון "מתעורר מעצמו", powercfg /lastwake מציג את סיבת ההשכמה האחרונה ו-powercfg /waketimers מציג טיימרים ששמורים כרגע להעיר את המחשב. במחשב Modern Standby, powercfg /sleepstudy מייצר דוח צריכה ופעילות בשינה. היסטוריית שינה וחזרה נרשמת גם ביומן האירועים (מקור Kernel-Power ביומן System), כך שאפשר לאשר בציר זמן "מתי ישן, ומתי ולמה התעורר".

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג