javamail в режиме ожидания перестает вызывать сообщения Добавлено через некоторое время, поток заблокирован

Я разрабатываю приложение для Android, которое получает и обрабатывает почтовые сообщения. Приложение должно быть подключено к серверу IMAP и поддерживать соединение, чтобы оно могло мгновенно видеть и обрабатывать новые почтовые сообщения (почта содержит данные json с почтового API-сервера). Приложение имеет два режима: ручное и живое подключение. Вот часть моего кода:

class Idler {
Thread th;
volatile Boolean isIdling=false;
boolean shouldsync=false;//we need to see if we have unseen mails
Object idleLock;
Handler handler=new Handler();
IMAPFolder inbox;
public boolean keppAliveConnection;//keep alive connection, or manual mode

//This thread should keep the idle connection alive, or in case it's set to manual mode (keppAliveConnection=false) get new mail.
Thread refreshThread;
synchronized void refresh()
{
    if(isIdling)//if already idling, just keep connection alive
    {
        refreshThread =new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    inbox.doCommand(new IMAPFolder.ProtocolCommand() {
                        @Override
                        public Object doCommand(IMAPProtocol protocol) throws ProtocolException {
                            //Why not noop?
                            //any call to IMAPFolder.doCommand() will trigger waitIfIdle, this
                            //issues a "DONE" command and waits for idle to return(ideally with a DONE server response).
                            // So... I think NOOP is unnecessary
                            //protocol.simpleCommand("NOOP",null); I'm not issuing noop due to what I said ^

                            //PD: if connection was broken, then server response will never arrive, and idle will keep running forever
                            //without triggering messagesAdded event any more :'( I see any other explanation to this phenomenon


                            return null;
                        }
                    });
                } catch (MessagingException e) {
                    e.printStackTrace();
                }
            }
        },"SyncThread");
        refreshThread.start();
    }
    else
    {
        getNewMail();//If manual mode keppAliveConnection=false) get the new mail
    }
}
public Idler()
{
    th=new Thread(new Runnable() {

        @SuppressWarnings("InfiniteLoopStatement")
        @Override
        public void run() {
            while (true)
            {
                try {
                    if(refreshThread !=null && refreshThread.isAlive())
                        refreshThread.interrupt();//if the refresher thread is active: interrupt. I thing this is not necessary at this point, but not shure
                    initIMAP();//initializes imap store
                    try {
                        shouldsync=connectIMAP()||shouldsync;//if was disconnected or ordered to sync: needs to sync
                    }
                    catch (Exception e)
                    {
                        Thread.sleep(5000);//if can't connect: wait some time and throw
                        throw e;
                    }
                    shouldsync=initInbox()||shouldsync;//if inbox was null or closed: needs to sync
                    if(shouldsync)//if needs to sync
                    {
                        getNewMail();//gets new unseen mail
                        shouldsync=false;//already refreshed, clear sync "flag"
                    }

                    while (keppAliveConnection) {//if sould keep idling "forever"
                        synchronized (idleLock){}//MessageCountListener may be doing some work... wait for it
                        isIdling = true; //set isIdling "flag"
                        handler.removeCallbacksAndMessages(null);//clears refresh scheduled tasks
                        handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                refresh();
                            }
                        },1200000);//Schedule a refresh in 20 minutes
                        inbox.idle();//start idling
                        if(refreshThread !=null && refreshThread.isAlive())
                            refreshThread.interrupt();//if the refresher thread is active: interrupt. I thing this is not necessary at this point, but not shure
                        handler.removeCallbacksAndMessages(null);//clears refresh scheduled tasks
                        isIdling=false;//clear isIdling "flag"
                        if(shouldsync)
                            break;//if ordered to sync... break. The loop will handle it upstairs.
                        synchronized (idleLock){}//MessageCountListener may be doing some work... wait for it

                    }
                }
                catch (Exception e) {
                    //if the refresher thread is active: interrupt
                    //Why interrupt? refresher thread may be waiting for idle to return after "DONE" command, but if folder was closed and throws
                    //a FolderClosedException, then it could wait forever...., so... interrupt.
                    if (refreshThread != null && refreshThread.isAlive())
                        refreshThread.interrupt();
                    handler.removeCallbacksAndMessages(null);//clears refresh scheduled tasks
                }
            }
        }
    },"IdlerThread");
    th.start();
}

private synchronized void getNewMail()
{
    shouldsync=false;
    long uid=getLastSeen();//get last unprocessed mail
    SearchTerm searchTerm=new UidTerm(uid,Long.MAX_VALUE);//search from las processed message to the las one.
    IMAPSearchOperation so=new IMAPSearchOperation(searchTerm);
    try {
        so.run();//search new messages
        final long[] is=so.uids();//get unprocessed messages count
        if (is.length > 0) {//if some...
            try {
                //there are new messages
                IMAPFetchMessagesOperation fop=new IMAPFetchMessagesOperation(is);
                fop.run();//fetch new messages
                if(fop.messages.length>0)
                {
                    //process fetched messages (internally sets the last seen uid value & delete some...)
                    processMessages(fop.messages);
                }
                inbox.expunge();//expunge deleted messages if any
            }
            catch (Exception e)
            {
                //Do something
            }
        }
        else
        {
            //Do something
        }
    }
    catch (Exception e)
    {
        //Do something
    }
}


private synchronized void initIMAP()
{
    if(store==null)
    {
        store=new IMAPStore(mailSession,new URLName("imap",p.IMAPServer,p.IMAPPort,null,p.IMAPUser,p.IMAPPassword));
    }
}

private boolean connectIMAP() throws MessagingException {
    try {
        store.connect(p.IMAPServer, p.IMAPPort, p.IMAPUser, p.IMAPPassword);
        return true;
    }
    catch (IllegalStateException e)
    {
        return false;
    }
}

//returns true if the folder was closed or null
private synchronized boolean initInbox() throws MessagingException {
    boolean retVal=false;
    if(inbox==null)
    {//if null, create. This is called after initializing store
        inbox = (IMAPFolder) store.getFolder("INBOX");
        inbox.addMessageCountListener(countListener);
        retVal=true;//was created
    }
    if(!inbox.isOpen())
    {
        inbox.open(Folder.READ_WRITE);
        retVal=true;//was oppened
    }
    return retVal;
}

private MessageCountListener countListener= new MessageCountAdapter() {
    @Override
    public void messagesAdded(MessageCountEvent ev) {
        synchronized (idleLock)
        {
            try {
                processMessages(ev.getMessages());//process the new messages, (internally sets the last seen uid value & delete some...)
                inbox.expunge();//expunge deleted messajes if any
            } catch (MessagingException e) {
                //Do something
            }

        }
    }
};

}

Проблема в следующем: иногда, когда пользователь обновляется или приложение автоматически обновляется в режиме активного подключения, одно или оба этих условия не позволяют моему приложению получать новые сообщения. Это из исходного кода javamail.

1: IdlerThread входит в состояние монитора в:

//I don't know why sometimes it enters monitor state here.
private synchronized void throwClosedException(ConnectionException cex) 
        throws FolderClosedException, StoreClosedException {
// If it's the folder's protocol object, throw a FolderClosedException;
// otherwise, throw a StoreClosedException.
// If a command has failed because the connection is closed,
// the folder will have already been forced closed by the
// time we get here and our protocol object will have been
// released, so if we no longer have a protocol object we base
// this decision on whether we *think* the folder is open.
if ((protocol != null && cex.getProtocol() == protocol) ||
    (protocol == null && !reallyClosed))
        throw new FolderClosedException(this, cex.getMessage());
    else
        throw new StoreClosedException(store, cex.getMessage());
}

2: RefresherThread переходит в состояние ожидания через:

  void waitIfIdle() throws ProtocolException {
assert Thread.holdsLock(messageCacheLock);
while (idleState != RUNNING) {
    if (idleState == IDLE) {
    protocol.idleAbort();
    idleState = ABORTING;
    }
    try {
    // give up lock and wait to be not idle
    messageCacheLock.wait();//<-----This is the line is driving me crazy.
    } catch (InterruptedException ex) { }
}
}

Поскольку один из обоих этих потоков «перестает» работать (состояние ожидания и мониторинга), мое приложение бесполезно при достижении этого состояния. В моей стране мобильная сеть передачи данных очень нестабильна, медленна и дорога (GSM). Поэтому она должна быть устойчивой к сбоям и заботиться о каждом передаваемом бите.

Я предполагаю, что проблема возникает, когда соединение молча обрывается, и refresherThread начинает выполнять свою работу. Он выдает команду DONE, если бездействие активно, но поскольку соединение пропало, когда бездействие пытается создать исключение FolderClosedException, один или оба потока блокируются на неопределенный срок.

Итак, мой вопрос: почему возникает такая ситуация и как ее предотвратить? Как обеспечить безопасную работу цикла бездействия без блокировки?

Я пробовал много вещей до изнеможения без каких-либо результатов.

Вот несколько тем, которые я прочитал, но не нашел решения своей проблемы. В моей стране интернет тоже ЧРЕЗВЫЧАЙНО дорог, поэтому я не могу исследовать столько, сколько хочу, и не могу перечислить все URL-адреса, которые я посетил в поисках информации.

JavaMail: поддержание активности IMAPFolder.idle()

JavaMail: поддержание активности IMAPFolder.idle()

Javamail: правильный способ выдачи idle() для IMAPFolder

Пожалуйста, извините за мой английский. Любое предложение будет принята с благодарностью. Я слышал о строгости этого сайта, поэтому, пожалуйста, будьте осторожны, я здесь новенький.


person Raymond Arteaga    schedule 08.10.2017    source источник


Ответы (1)


Обязательно установите свойства тайм-аута, чтобы убедиться, что вы не зависаете в ожидании мертвого соединения или сервера.

Вместо прямого ввода команды nop следует вызвать Folder.isOpen или Folder.getMessageCount; они выдадут команду nop, если это необходимо.

Если папка закрыта асинхронно (FolderClosedException), вам потребуется перезапустить цикл простоя.

person Bill Shannon    schedule 08.10.2017
comment
Действительно, я думаю, что это происходит, когда где-то выбрасывается исключение FolderClosedException (слишком много беспорядка для отладки). Что вы имеете в виду под перезапуском цикла простоя? - person Raymond Arteaga; 10.10.2017
comment
Если папка закрыта асинхронно, вызов Folder.idle завершится ошибкой. Это может оставить этот поток в замкнутом цикле, поскольку вы игнорируете все исключения. Было бы лучше поймать это исключение, снова открыть папку (если это то, что вы намереваетесь), а затем перезапустить цикл простоя. - person Bill Shannon; 10.10.2017
comment
Код больше, чем фрагмент, который я разместил. Все это уже реализовано, но я хотел, чтобы это было кратко. Думаю, я решил проблему, но предполагаемое объяснение того, что, по моему мнению, происходит, не вписывается в допустимое количество символов. Есть ли правильный способ добавить комментарий большего размера? - person Raymond Arteaga; 12.10.2017
comment
Обновите исходный пост. - person Bill Shannon; 12.10.2017
comment
Я отредактировал свой вопрос, добавив более пояснительный текст. Пожалуйста, взгляните на это. Это сводит меня с ума. @БиллШеннон - person Raymond Arteaga; 13.10.2017
comment
Я не вижу никаких настроек свойств тайм-аута в вашем коде. Без правильной настройки соединения могут зависать, вызывая проблемы, которые вы видите. - person Bill Shannon; 14.10.2017
comment
Я читал, что нет возможности установить время простоя в свойствах сеанса, но если я установлю время ожидания сокета на 20 минут, а пользователь обновит приложение менее чем за 20 минут, как это поможет? это условие ошибки еще должно быть достигнуто... не должно? Хорошо.. он будет висеть менее 20 минут... но все же, это не то, что мне нужно. @Счет - person Raymond Arteaga; 14.10.2017
comment
Если связь мертва, вы не хотите ждать вечно. Вероятно, вам следует установить время ожидания менее 20 минут. Дело не в тайм-ауте простоя, а в том, как долго ждать ответа от сервера. Если команда бездействия достигает этого тайм-аута, она просто повторяет попытку. Вам также может понадобиться установить время ожидания записи. - person Bill Shannon; 14.10.2017