From f6ae96f6f067a7cab4d7ad0f9e8464e5b0937695 Mon Sep 17 00:00:00 2001 From: Michael Hohmuth Date: Fri, 24 Apr 2026 13:56:07 +0200 Subject: [PATCH] Simplify handling of dead connections to make it more robust. IMAPServer.acquireconnection: If connection turns out to be dead, release and recursively reacquire it. It turns out that the fix in 680d8742e2eae5dcc220c02139d2836b88a3f352 was not sufficient and perhaps a bit over-engineered: It resulted in prolonged unhealthy sockets. This new fix uses the same basic idea (call acquireconnection recursively to return a healthy socket) but simply and unceremoniously calls releaseconnection on the unhealthy one. How, unhealthy sockets are detected and fixed quickly. Signed-off-by: Michael Hohmuth --- offlineimap/imapserver.py | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py index 5e447b87..7c0358e9 100644 --- a/offlineimap/imapserver.py +++ b/offlineimap/imapserver.py @@ -62,20 +62,6 @@ def _is_socket_alive(imapobj): return False -def _check_pooled_connection(imapobj, ui): - """Verify that a pooled connection is still alive by sending a NOOP command. - Returns True if the connection is healthy and False if it is not. Logs - any exceptions encountered during the NOOP command as debug messages, since - they are expected to occur when a connection has gone stale. - """ - try: - typ, _ = imapobj.noop() - return typ == 'OK' - except Exception as e: - ui.debug('imap', 'Pooled connection health check (NOOP) failed: %s' % e) - return False - - class IMAPServer: """Initializes all variables from an IMAPRepository() instance @@ -596,19 +582,12 @@ def acquireconnection(self): # Verify that the connection is still alive before returning it # to the caller. If not, clean up and recursively call # acquireconnection() to get a new one. - if not _check_pooled_connection(imapobj, self.ui): - self.ui.debug('imap', 'Pooled connection to %s is dead, ' - 'creating a new one' % self.hostname) - # Clean up and release the slot to force a new connection - self.connectionlock.acquire() - self.assignedconnections.remove(imapobj) - self.connectionlock.release() - try: - imapobj.logout() - except Exception: - pass - self.semaphore.release() - return self.acquireconnection() # Recursive call to get a new connection + try: + imapobj.noop() + except imapobj.abort: + self.ui.warn('Connection %s is dead, dropping' % imapobj.identifier) + self.releaseconnection(imapobj, True) + return self.acquireconnection() return imapobj