תמיכה בריבוי תהליכים בלקוחות הדוא"ל

לקוחות דואר כגון ImapClient ו Pop3Client מאפשר למשתמשים להשתמש בהם בסביבת ריבוי תהליכים. לקוחות דואר מאפשרים קיום של חיבור אחד או יותר עם שרת. לניהול קבוצת החיבורים בתוך הלקוחות משתמשים במאגר חיבורים. כמות החיבורים שניתן ליצור ולהשתמש בהם במקביל מוגבלת על ידי המאפיין CredentialsByHostClient.MaxConnectionsPerServer. ניתן להגדיר מאפיין זה ל‑1 או ערך גבוה יותר. ברירת המחדל היא 10. עבור החיבור יושם תור פקודות לתמיכה בפעולות ריבוי‑תהליכים. הפקודות מממשות פעולות פשוטות בפרוטוקול, כגון Noop, Authenticate ועוד. המשתמש יכול להתחיל ביצוע כמות גדולה של פקודות מעבר למספר החיבורים הזמינים, אך הן יתבצעו רק כאשר הלקוח יוכל ליצור חיבור לפעולה.

שיטות שימוש בלקוחות דוא"ל בסביבת ריבוי תהליכים

ללקוחות דוא"ל יש את ההתנהגות הבאה:

1. במקרה שה‑MaxConnectionsPerServer = 1, הלקוח יוצר חיבור אחד, מבצע אימות והauthorization. חיבור זה נשמר במצב פעילות עד שהלקוח יושמד. כל הפעולות מתהליכים שונים מופנות לתור פקודות יחיד הממוקם בחיבור הראשי.

2. במקרה שה‑MaxConnectionsPerServer > 1, הלקוח יוצר את מספר החיבורים הנדרש, מבצע אימות והרשאה לכל חיבור. חיבור אחד שמור כחיבור ראשי. חיבור זה נשמר במצב פעילות עד שהלקוח יתבצע השמדתו. כל שאר החיבורים נוצרים ומשתלמים לפי דרישה. הכמות המרבית של חיבורים כאלה מוגדרת על ידי המאפיין MaxConnectionsPerServer, לדוגמה אם MaxConnectionsPerServer = 2, אז אחד שמור כחיבור ראשי, והחיבור השני משמש כתוספת לפעולות המבוצעות בתהליכים אחרים. באופן דומה, אם MaxConnectionsPerServer = 3, החיבור הראשון שמור כחיבור ראשי, ושניים נוספים משמשים כתוספת לפעולות בתהליכים אחרים. במצב שבו מתבקשת חיבור נוסף מתהליך חדש, וכל החיבורים כבר תפוסים, הלקוח מחכה עד שמספר החיבורים בשימוש יופחת. זהו רגע חשוב שמבהיר מדוע השמדת חיבורים נכונה כה משמעותית.

דוגמאות

המשתמש יכול לבצע פעולות בתהליכים שונים בכמה דרכים. ניתן לחלק אותן לשני סוגים:

1. המשתמש משתמש במתודות אסינכרוניות (Begin/End) המוגדרות בלקוח. במקרה זה לקוח הדוא"ל משגר תהליכים חדשים לפי הצורך. בלקוח יש תור משימות (אל תבלבלו עם תור פקודות בחיבור). משימה יכולה להתבצע אם יש חיבור זמין. כאשר מספר החיבורים בשימוש הופך לפחות מאשר ערך המגבלה, הלקוח יוצר חיבור חדש, יוצר תהליך עבור המשימה הנוכחית ומבצע אותה. דוגמה לשימוש בפעולות אסינכרוניות:

// Create an imapclient with host, user and password
ImapClient client = new ImapClient();
client.setHost("domain.com");
client.setUsername("username");
client.setPassword("password");
client.selectFolder("InBox");

ImapMessageInfoCollection messages = client.listMessages();
IAsyncResult res1 = client.beginFetchMessage(messages.get_Item(0).getUniqueId());
IAsyncResult res2 = client.beginFetchMessage(messages.get_Item(1).getUniqueId());
MailMessage msg1 = client.endFetchMessage(res1);
MailMessage msg2 = client.endFetchMessage(res2);

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

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

/**
 * Creates an executor service with a fixed pool size, that will time 
 * out after a certain period of inactivity.
 * 
 * @param poolSize The core- and maximum pool size
 * @param keepAliveTime The keep alive time
 * @param timeUnit The time unit
 * @return The executor service
 */
private static ExecutorService createFixedTimeoutExecutorService(
    int poolSize, long keepAliveTime, TimeUnit timeUnit)
{
    ThreadPoolExecutor e = 
        new ThreadPoolExecutor(poolSize, poolSize,
            keepAliveTime, timeUnit, new LinkedBlockingQueue<Runnable>());
    e.allowCoreThreadTimeOut(true);
    return e;
}

final List<MailMessage> list = new ArrayList<MailMessage>();

ExecutorService executor = createFixedTimeoutExecutorService(1, 1000, TimeUnit.MILLISECONDS);

executor.execute(new Runnable() {
    public void run() {
        client.selectFolder("folderName");
        ImapMessageInfoCollection messageInfoCol = client.listMessages();
        for (ImapMessageInfo messageInfo : messageInfoCol) {
            list.add(client.fetchMessage(messageInfo.getUniqueId()));
        }
    }
});

b. כאשר המשתמש מריץ שיטה ליצירת חיבור חדש לתהליל נוסף, תהליך זה חסום עד ששינוי ערך המquota לחיבורים חדשים יאפשר חיבור חדש. לאחר מכן נוצר חיבור חדש. חיבור זה מוגדר כחיבור ברירת המחדל לכל הפעולות בתהליך זה. לאחר שמסיימים את כל הפעולות בתהליך זה, יש לפנות את החיבור. כדי ליצור חיבורים חדשים השתמשו במתודה CredentialsByHostClient.CreateConnection. מתודה זו מחזירה אובייקט שמממש את הממשק IDisposable. לשחרור החיבור יש לקרוא למתודה Dispose. יצירת וחיסול חיבור חייבים להתבצע בתוך התהליך שבו מתבצעות פעולות הדוא"ל. ניסיון ליצור חיבור חדש בתהליך שבו נוצר לקוח הדוא"ל מוביל לשגיאה, מכיוון שתהליך זה במועד זה אינו יכול לשמש ליצירת חיבור חדש. כמו‑כן, יצירת חיבור חדש אינה אפשרית כאשר MaxConnectionsPerServer = 1. דוגמת קוד ליצירת חיבור חדש בתהליך נוסף:

final List<MailMessage> list = new ArrayList<MailMessage>();

ExecutorService executor = createFixedTimeoutExecutorService(1, 1000, TimeUnit.MILLISECONDS);

executor.execute(new Runnable() {
    public void run() {
        IConnection connection = client.createConnection();
        try {
            client.selectFolder(connection, "FolderName");
            ImapMessageInfoCollection messageInfoCol = client.listMessages(connection);
            for (ImapMessageInfo messageInfo : messageInfoCol)
                list.add(client.fetchMessage(connection, messageInfo.getUniqueId()));
        } finally {
            connection.dispose();
        }
    }
});

המלצות

שווה לציין שאם המשתמש שולח את כל הפקודות דרך החיבור הראשי, ייתכנה מצב שבו פקודות משThreads שונים מתערבבות. על המשתמש להבין אילו פקודות תלויות ברצף שלהן ולנקוט אמצעים לסנכרון של פקודות אלו. יש גם לקחת בחשבון אפשרות לביצוע פקודות במפגשים שונים (IMAP/POP3). הפעולות שלוקחות את רוב הזמן, כגון FetchMessage, AppendMessage ו‑Send, ככל הנראה יש לבצע אותן באמצעות תהליך חדש וחיבור חדש. אך פעולות מהירות כגון Delete עדיף לבצען דרך החיבור הראשי. יש להבחין בכך שהאתחול של חיבור חדש הוא פעולה שגוזלת זמן.