JAVA, NIO, Selector לא מצליח לפתור את הבאג

Guy Yafe

New member
JAVA, NIO, Selector לא מצליח לפתור את הבאג

מטרת התרחיש היא לבדוק הדלקה וכיבוי של השרת.
התרחיש:
  1. פותח thread עבור שרת.
    1. יוצר Selector ויוצר ServerSocketChannel ומצמד אותו ל - Selector.
    2. ה - ServerSocketChannel אחראי לקבל חיבורים נכנסים. בכל חיבור הוא מייצר SocketChannel חדש ומצמד אותו ל - Selector. (אותו Selector מאזין גם ל - ServerSocket וגם לכל ה - Socket-ים).
    3. כל אחד מה - SocketChannel-ים קורא מידע מהרשת וכותב אותו למערך שלא עושה שום דבר.
  2. פותח עשרים thread-ים עבור ה - clients. כל אחד מהם מתחבר לשרת, ומבצע read. ברגע שה - read חוזר, הוא מוודא שהתקבל 1- (כלומר השרת סגר את החיבור), ואז סוגר את ה - socket.
  3. ב - thread הראשי ממתין שלוש שניות כדי שכל החיבורים יתייצבו ואז עובר על ה - selector.keys() ועבור כל אחד מהם מבצע close ל - channel ואז cancel.
  4. מבצע wakeup לselector כדי שיסיים את הלולאה שלו ויסגור את השרת
הייתי מצפה שלאחר הסגירה של ה - channels מתוך הThread הראשי, כל ה - clients יתעוררו, יראו שסגרו אותם ויסיימו. לאחר מכן השרת יתעורר, יראה שהוא לא אמור להמשיך לעבוד ויסגר בעצמו.

הבאג:
אני מקבל ConcurrentModificationException במעבר על הלולאה שסוגרת את כל ה - channels.
אין לי מושג למה אני מקבל את ה - exception הזה.
שמתי לב שבזמן הסגירה של ה - channels, ה - selector מתעורר, ואני לא מצליח להבין למה.
כשאני מכבה את השורה ב - client שמבצעת close ל - socket לאחר הסגירה שלו מצד השרת, הבאג נעלם, ולכן העליתי את ההשערה שמאיזושהי סיבה close מצד ה-client גורם ל - select להתעורר. זה מפתיע כי ה - key שמקושר לאותו socket כבר עבר cancel.
כשה - select מתעורר אני בודק מי נמצא ב - selectedKeys. אם הספקתי לסגור את ה - serverSocketChannel, הקבוצה הזו תהיה ריקה. אם לא הספקתי לסגור אותו, המפתח שלו יהיה היחיד בקבוצה.
אבחנה נוספת היא שאם אני מעתיק את כל המפתחות מתוך ה - selector.keys לתוך רשימה נפרדת, אני לא אקבל את אותו CME. זה מרגיש לי כמו workaround כי אני עדיין לא מבין מי נכנס לset הזה ונוגע בו בזמן שאני עובר עליו. הייתי משער שה - selector עושה זאת אחרי שהוא מתעורר אבל אז אני עדיין צריך להבין מי מעיר אותו.


לסיכום:
המסקנה שלי היא שברגע שה - client סוגר את ה - socket ה - selector מתעורר, אף על פי שהתבצע cancel ל - channel שמשוייך לאותו client.
ברגע שה - selector מתעורר, הוא נוגע איכשהו ב - key שלו וגורם ל - CME.
זה מוביל לשתי שאלות:
  1. מדוע ה - selector מתעורר בזמן סגירת ה - client (אם בכלל זה מה שקורה) ומדוע ה - selectedKeys שלו ריק או מכיל לכל היותר את המפתח של ServerSocketChannel?
  2. מי עוד נוגע באותו keySet ולכן גורם ל - CME?
תפוז לא נותנים להוסיף קוד אז הוא מצורף בקישור הבא:
http://pastebin.com/2xP7t8qu

הדפסות לאחר הרצה (שימו לב שבאחת ההדפסות רשימת המפתחות שאיתן ה - selector התעורר היא ריקה, ובשנייה היא מלאה):
http://pastebin.com/qw6j9bTH

אם למישהו יש רעיון למה שקורה כאן, אשמח לכל עזרה.
 

selalerer

New member
קראתי את ההודע וקראתי את הקוד. לא הבנתי כלום. בלאגן.

עם כזה בלאגן, הייתי מופתע אם זה היה עובד.

מה אתה עושה? מה המטרה? מה התוכנה הזאת אמורה להשיג?

טיפ קטן להתחלה: לא מסנכרנים עם sleep.
 

Guy Yafe

New member
בלגן? דווקא ניסיתי לזקק כמה שיותר דברים לא קשורים.

אני בונה שרת שמקבל הודעות מכל מיני חיבורים.
כשנתקלתי בבאג הזה, לקחתי רק את הקוד הרלוונטי והעברתי ומתודת main כדי שאוכל לפרסם רק אותה.
בפועל כל חיבור כמובן מדבר עם ה - client ושואב ממנו מידע, אבל זה לא רלוונטי לבאג עצמו.
&nbsp
מטרת התכנה שפרסמתי היא לחבר client-ים, ואז לסגור את כולם מהצד של השרת ורק לוודא שהתוכנה לא מתרסקת.
&nbsp
לגבי הסנכרון על sleep:אתה כמובן צודק, אבל זה היה הכי פשוט בשביל הדוגמה הקטנה הזו.
 

Guy Yafe

New member
נמצאה הבעיה

מסתבר שאם ה - client סוגר את ה - socket בצד שלו (וזה לא משנה אם השרת סגר את אותו socket קודם או לא), ה - selector מתעורר עם המפתח של ה - serverSocketChannel. פעולת accept אמנם תחזיר NULL, אבל יש לקחת אותה בחשבון.
אני לא לגמרי בטוח למה זה קורה, הייתי משער שברגע ש - socket נסגר בצד של השרת, אין סיבה שהצד של הלקוח יטריג אותו שוב, אבל זו העובדה.
בפתרון הוא כנראה להפריד בין ה - selector שמקבל חיבורים חדשים לבין ה - selector שמאזין לאותם חיבורים.
 
למעלה