חלוקת השיחה לשרשורים

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

שיחות במצב העברה (passthrough)

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

שרשורים ב-HALs מצורפים

כדי לטפל בקריאות RPC נכנסות (כולל קריאות חזרה אסינכררוניות מ-HAL למשתמשי HAL) ובהודעות על מוות, מאגר משימות משויך לכל תהליך שמשתמש ב-HIDL. אם תהליך יחיד מטמיע כמה ממשקי HIDL ו/או מנהלים של הודעות מוות, מאגר השרשור שלו משותף לכל הגורמים האלה. כשתהליך מקבל קריאה נכנסת ל-method מלקוח, הוא בוחר חוט פנוי ממאגר החוטים ומבצע את הקריאה בחוט הזה. אם אין חוט פנוי, הוא נחסם עד שאחד יהיה זמין.

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

מספר קריאות בתצוגת עץ נשלחות באותו חוט hwbinder. לדוגמה, אם תהליך (A) מבצע קריאה סינכרונית מ-hwbinder thread לתהליך (B), ואז תהליך (B) מבצע קריאה סינכרונית חזרה לתהליך (A), הקריאה מתבצעת ב-hwbinder thread המקורי ב-(A) שנחסם בקריאה המקורית. האופטימיזציה הזו מאפשרת לשרת עם שרשור יחיד לטפל בקריאות בתצוגת עץ, אבל היא לא חלה על מקרים שבהם הקריאות עוברות דרך רצף אחר של קריאות IPC. לדוגמה, אם תהליך (B) ביצע קריאה ל-binder/vndbinder שגרמה לקריאה לתהליך (C), ואז תהליך (C) חוזר לקריאה ל-(A), אי אפשר להגיש אותה בשרשור המקורי ב-(A).

מודל של תהליכים בשרת

מלבד מצב העברה (passthrough), הטמעות שרת של ממשקי HIDL נמצאות בתהליך שונה מהלקוח, וצריך להשתמש בשרשור אחד או יותר שמחכה לקריאות נכנסות של שיטות. השרשור האלה הם מאגר השרשור של השרת. השרת יכול להחליט כמה שרשור הוא רוצה להריץ במאגר השרשור שלו, והוא יכול להשתמש בגודל של מאגר שרשור אחד כדי לסדר את כל הקריאות בממשקים שלו. אם יש לשרת יותר משרשור אחד במאגר השרשור, הוא יכול לקבל קריאות נכנסות בו-זמנית בכל אחד מהממשקים שלו (ב-C++‎, המשמעות היא שצריך לנעול בזהירות את הנתונים המשותפים).

קריאות חד-כיווניות לאותו ממשק עוברות סריאליזציה. אם לקוח עם כמה שרשורים קורא ל-method1 ול-method2 בממשק IFoo, ול-method3 בממשק IBar, הקריאות ל-method1 ול-method2 תמיד יתבצעו בסדרה, אבל הקריאה ל-method3 יכולה לפעול במקביל לקריאות ל-method1 ול-method2.

שרשור אחד של ביצוע לקוח יכול לגרום לביצוע בו-זמנית בשרת עם כמה שרשורים בשתי דרכים:

  • שיחות מ-oneway לא נחסמות. אם מתבצעת קריאה ל-oneway ואז קריאה ל-non-oneway, השרת יכול לבצע את הקריאה ל-oneway ואת הקריאה ל-non-oneway בו-זמנית.
  • שיטות שרת שמעבירות נתונים בחזרה באמצעות קריאות חזרה (callbacks) סינכרוניות יכולות לבטל את החסימה של הלקוח ברגע שהקריאה החוזרת מופעל מהשרת.

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

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

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

הערה: מומלץ מאוד שפונקציות שרת יחזרו מיד אחרי שהן מפעילות את פונקציית הקריאה החוזרת.

לדוגמה (ב-C++):

Return<void> someMethod(someMethod_cb _cb) {
    // Do some processing, then call callback with return data
    hidl_vec<uint32_t> vec = ...
    _cb(vec);
    // At this point, the client's callback is called,
    // and the client resumes execution.
    ...
    return Void(); // is basically a no-op
};

מודל של תהליכים בלקוח

מודל השרשור בלקוח שונה בין קריאות לא חסימניות (פונקציות שמסומנות במילות המפתח oneway) לבין קריאות חסימניות (פונקציות שלא צוינה בהן מילת המפתח oneway).

חסימת שיחות

כשחוסמים שיחות, הלקוח חוסם עד שאחד מהאירועים הבאים מתרחש:

  • מתרחשת שגיאת תעבורה. אובייקט Return מכיל מצב שגיאה שאפשר לאחזר באמצעות Return::isOk().
  • הטמעת השרת קוראת ל-callback (אם היה כזה).
  • הטמעת השרת מחזירה ערך (אם לא היה פרמטר של קריאה חוזרת).

במקרה של הצלחה, השרת תמיד קורא לפונקציית הקריאה החוזרת שהלקוח מעביר כארגומנטים לפני שהפונקציה עצמה מחזירה. ה-callback מופעל באותו חוט שבו מתבצעת הקריאה לפונקציה, ולכן למטמיעים צריך להיזהר כשהם מחזיקים מנעולים במהלך קריאות לפונקציות (ולהתחמק מהם לגמרי כשאפשר). פונקציה ללא הצהרת generates או מילת מפתח oneway עדיין חוסמת. הלקוח נחסם עד שהשרת מחזיר אובייקט Return<void>.

שיחות חד-כיווניות

כשפונקציה מסומנת בתווית oneway, הלקוח חוזר מיד ולא מחכה שהשרת ישלים את הקריאה לפונקציה. באופן כללי (ובאופן מצטבר), המשמעות היא שקריאת הפונקציה נמשכת חצי מהזמן כי היא מבצעת חצי מהקוד, אבל כשכותבים הטמעות שרגישות לביצועים, יש לכך השלכות מסוימות על תזמון. בדרך כלל, שימוש בקריאה חד-כיוונית גורם לכך שהקריאה ממשיכה להיות מתוזמנת, ואילו שימוש בקריאה סינכרונית רגילה גורם למתזמן להעביר מיד מהקריאה לתהליך הנמען. זוהי אופטימיזציה של הביצועים ב-binder. בשירותים שבהם צריך להריץ את הקריאה החד-כיוונית בתהליך היעד עם קדימות גבוהה, אפשר לשנות את מדיניות התזמון של השירות המקבל. ב-C++‎, שימוש בשיטה setMinSchedulerPolicy של libhidltransport עם העדיפויות והמדיניות של מתזמן המשימות שמוגדרים ב-sched.h מבטיח שכל הקריאות לשירות יפעלו לפחות לפי העדיפות והמדיניות שהוגדרו לתזמון.