Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: imap connection caching and reuse #10108

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/BackgroundJob/MigrateImportantJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public function run($argument) {
$this->logger->debug('Could not flag messages from DB on IMAP for mailbox <' . $mailboxId . '>.');
}
} finally {
$client->logout();
$this->imapClientFactory->destroyClient($account);
}
}
}
10 changes: 9 additions & 1 deletion lib/BackgroundJob/PreviewEnhancementProcessingJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace OCA\Mail\BackgroundJob;

use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\PreprocessingService;
use OCP\AppFramework\Db\DoesNotExistException;
Expand All @@ -30,7 +31,8 @@ public function __construct(ITimeFactory $time,
AccountService $accountService,
PreprocessingService $preprocessingService,
LoggerInterface $logger,
IJobList $jobList) {
IJobList $jobList,
private IMAPClientFactory $imapClientFactory) {
parent::__construct($time);

$this->userManager = $userManager;
Expand Down Expand Up @@ -73,6 +75,12 @@ public function run($argument) {
}

$limitTimestamp = $this->time->getTime() - (60 * 60 * 24 * 14); // Two weeks into the past

// create client in memory cache
$this->imapClientFactory->getClient($account);
// process data
$this->preprocessingService->process($limitTimestamp, $account);
// destroy client in memory cache
$this->imapClientFactory->destroyClient($account);
}
}
10 changes: 9 additions & 1 deletion lib/BackgroundJob/QuotaJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace OCA\Mail\BackgroundJob;

use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\Account;
use OCA\Mail\Contracts\IMailManager;
use OCA\Mail\Service\AccountService;
Expand All @@ -34,7 +35,8 @@ public function __construct(ITimeFactory $time,
IMailManager $mailManager,
IManager $notificationManager,
LoggerInterface $logger,
IJobList $jobList) {
IJobList $jobList,
private IMAPClientFactory $imapClientFactory) {
parent::__construct($time);

$this->userManager = $userManager;
Expand Down Expand Up @@ -77,7 +79,13 @@ protected function run($argument): void {
return;
}

// create client in memory cache
$this->imapClientFactory->getClient($account);
// process data
$quota = $this->mailManager->getQuota($account);
// destroy client in memory cache
$this->imapClientFactory->destroyClient($account);

if ($quota === null) {
$this->logger->debug('Could not get quota information for account <' . $account->getEmail() . '>', ['app' => 'mail']);
return;
Expand Down
9 changes: 8 additions & 1 deletion lib/BackgroundJob/SyncJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Horde_Imap_Client_Exception;
use OCA\Mail\Exception\IncompleteSyncException;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\IMAP\MailboxSync;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\Sync\ImapToDbSynchronizer;
Expand Down Expand Up @@ -40,7 +41,8 @@ public function __construct(ITimeFactory $time,
ImapToDbSynchronizer $syncService,
LoggerInterface $logger,
IJobList $jobList,
IConfig $config) {
IConfig $config,
private IMAPClientFactory $imapClientFactory) {
parent::__construct($time);

$this->userManager = $userManager;
Expand Down Expand Up @@ -89,8 +91,13 @@ protected function run($argument) {
}

try {
// create client in memory cache
$this->imapClientFactory->getClient($account);
// sync mailboxes and messages
$this->mailboxSync->sync($account, $this->logger, true);
$this->syncService->syncAccount($account, $this->logger);
// destroy client in memory cache
$this->imapClientFactory->destroyClient($account);
} catch (IncompleteSyncException $e) {
$this->logger->warning($e->getMessage(), [
'exception' => $e,
Expand Down
2 changes: 1 addition & 1 deletion lib/BackgroundJob/TrashRetentionJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ private function cleanTrash(Account $account, int $retentionSeconds): void {
);
}
} finally {
$client->logout();
$this->clientFactory->destroyClient($account);
}
}
}
2 changes: 1 addition & 1 deletion lib/Command/DiagnoseAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->writeln('<error>Horde error occurred: ' . $e->getMessage() . '. See nextcloud.log for more details.</error>');
return 2;
} finally {
$imapClient->logout();
$this->clientFactory->destroyClient($account);
}

return 0;
Expand Down
2 changes: 1 addition & 1 deletion lib/Controller/ListController.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public function unsubscribe(int $id): JsonResponse {
]);
return JsonResponse::error('Unknown error');
} finally {
$client->logout();
//$client->logout();
}

return JsonResponse::success();
Expand Down
4 changes: 2 additions & 2 deletions lib/Controller/MessageApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ public function get(int $id): DataResponse {
$message->getUid()
);
} finally {
$client->logout();
//$client->logout();
}

$json = $imapMessage->getFullMessage($id, $loadBody);
Expand Down Expand Up @@ -339,7 +339,7 @@ public function getRaw(int $id): DataResponse {
$this->logger->error('Message not found on IMAP, or mail server went away', ['exception' => $e->getMessage()]);
return new DataResponse('Message not found', Http::STATUS_NOT_FOUND);
} finally {
$client->logout();
//$client->logout();
}

return new DataResponse($source, Http::STATUS_OK);
Expand Down
8 changes: 4 additions & 4 deletions lib/Controller/MessagesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public function getBody(int $id): JSONResponse {
);
$json = $imapMessage->getFullMessage($id);
} finally {
$client->logout();
//$client->logout();
}

$itineraries = $this->itineraryService->getCached($account, $mailbox, $message->getUid());
Expand Down Expand Up @@ -485,7 +485,7 @@ public function getSource(int $id): JSONResponse {
)
]);
} finally {
$client->logout();
//$client->logout();
}

// Enable caching
Expand Down Expand Up @@ -524,7 +524,7 @@ public function export(int $id): Response {
$message->getUid()
);
} finally {
$client->logout();
//$client->logout();
}

return new AttachmentDownloadResponse(
Expand Down Expand Up @@ -573,7 +573,7 @@ public function getHtmlBody(int $id, bool $plain = false): Response {
$id
);
} finally {
$client->logout();
//$client->logout();
}

$htmlResponse = $plain ?
Expand Down
37 changes: 36 additions & 1 deletion lib/IMAP/IMAPClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
use function md5;

class IMAPClientFactory {

protected array $clientCache = [];

/** @var ICrypto */
private $crypto;

Expand All @@ -53,6 +56,17 @@ public function __construct(ICrypto $crypto,
$this->timeFactory = $timeFactory;
}

public function __destruct() {
$this->clientCache;
array_walk($this->clientCache, function($client) {
try {
$client->logout();
} catch (\Throwable $e) {
// Ignore any errors
}
});
}

/**
* Get the connection object for the given account
*
Expand All @@ -65,6 +79,14 @@ public function __construct(ICrypto $crypto,
* @return Horde_Imap_Client_Socket
*/
public function getClient(Account $account, bool $useCache = true): Horde_Imap_Client_Socket {

// generate connection signature
$clientSignature = md5($account->getMailAccount()->getInboundHost() . $account->getMailAccount()->getInboundUser());
// determine if connection is already cached and return cached object
if (isset($this->clientCache[$clientSignature])) {
return $this->clientCache[$clientSignature];
}

$this->eventDispatcher->dispatchTyped(
new BeforeImapClientCreated($account)
);
Expand Down Expand Up @@ -136,7 +158,20 @@ public function getClient(Account $account, bool $useCache = true): Horde_Imap_C
if ($rateLimitingCache instanceof IMemcache) {
$client->enableRateLimiter($rateLimitingCache, $paramHash, $this->timeFactory);
}

// cache client for reuse
$this->clientCache[$clientSignature] = $client;

return $this->clientCache[$clientSignature];
}

return $client;
public function destroyClient(Account $account) {
// generate connection signature
$clientSignature = md5($account->getMailAccount()->getInboundHost() . $account->getMailAccount()->getInboundUser());
// determine if connection is already cached and destroy it
if (isset($this->clientCache[$clientSignature])) {
$this->clientCache[$clientSignature]->logout();
unset($this->clientCache[$clientSignature]);
}
}
}
4 changes: 2 additions & 2 deletions lib/IMAP/MailboxSync.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public function sync(Account $account,
new MailboxesSynchronizedEvent($account)
);
} finally {
$client->logout();
//$client->logout();
}
}

Expand All @@ -156,7 +156,7 @@ public function syncStats(Account $account, Mailbox $mailbox): void {
$e
);
} finally {
$client->logout();
//$client->logout();
}

if (!isset($allStats[$mailbox->getName()])) {
Expand Down
2 changes: 1 addition & 1 deletion lib/IMAP/PreviewEnhancer.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public function process(Account $account, Mailbox $mailbox, array $messages): ar

return $messages;
} finally {
$client->logout();
//$client->logout();
}

return $this->mapper->updatePreviewDataBulk(...array_map(static function (Message $message) use ($data) {
Expand Down
2 changes: 1 addition & 1 deletion lib/IMAP/Search/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function findMatches(Account $account,
} catch (Horde_Imap_Client_Exception $e) {
throw new ServiceException('Could not get message IDs: ' . $e->getMessage(), 0, $e);
} finally {
$client->logout();
//$client->logout();
}

return $fetchResult['match']->ids;
Expand Down
2 changes: 1 addition & 1 deletion lib/Listener/DeleteDraftListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ private function deleteDraft(Account $account, Message $draft): void {
$this->logger->warning("Account has no draft mailbox set, can't delete the draft");
return;
} finally {
$client->logout();
//$client->logout();
}

try {
Expand Down
2 changes: 1 addition & 1 deletion lib/Send/CopySentMessageHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public function process(Account $account, LocalMessage $localMessage): LocalMess
]);
return $localMessage;
} finally {
$client->logout();
//$client->logout();
}

return $this->processNext($account, $localMessage);
Expand Down
2 changes: 1 addition & 1 deletion lib/Send/FlagRepliedMessageHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public function process(Account $account, LocalMessage $localMessage): LocalMess

}
} finally {
$client->logout();
//$client->logout();
}

return $this->processNext($account, $localMessage);
Expand Down
8 changes: 4 additions & 4 deletions lib/Service/AiIntegrations/AiIntegrationsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public function summarizeThread(Account $account, string $threadId, array $messa
}, $messages);

} finally {
$client->logout();
//$client->logout();
}

$taskPrompt = implode("\n", $messagesBodies);
Expand Down Expand Up @@ -141,7 +141,7 @@ public function generateEventData(Account $account, string $threadId, array $mes
return $imapMessage->getPlainBody();
}, $messages);
} finally {
$client->logout();
//$client->logout();
}

$task = new Task(
Expand Down Expand Up @@ -189,7 +189,7 @@ public function getSmartReply(Account $account, Mailbox $mailbox, Message $messa
$messageBody = $imapMessage->getPlainBody();

} finally {
$client->logout();
//$client->logout();
}
$prompt = "You are tasked with formulating helpful replies or reply templates to e-mails provided that have been sent to me. If you don't know some relevant information for answering the e-mails (like my schedule) leave blanks in the text that can later be filled by me. You must write the replies from my point of view as replies to the original sender of the provided e-mail!

Expand Down Expand Up @@ -255,7 +255,7 @@ public function requiresFollowUp(
true,
);
} finally {
$client->logout();
//$client->logout();
}

if (!$this->isPersonalEmail($imapMessage)) {
Expand Down
4 changes: 2 additions & 2 deletions lib/Service/AntiSpamService.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public function sendReportEmail(Account $account, Mailbox $mailbox, int $uid, st
$userId
);
} finally {
$client->logout();
//$client->logout();
}

$message->addEmbeddedMessageAttachment(
Expand Down Expand Up @@ -204,7 +204,7 @@ public function sendReportEmail(Account $account, Mailbox $mailbox, int $uid, st
} catch (Horde_Imap_Client_Exception $e) {
$this->logger->error('Could not move report email to sent mailbox, but the report email was sent. Reported email was id: #' . $messageId, ['exception' => $e]);
} finally {
$client->logout();
//$client->logout();
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/Service/DkimService.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function validate(Account $account, Mailbox $mailbox, int $id): bool {
throw new ServiceException('Could not fetch message source for uid ' . $id);
}
} finally {
$client->logout();
//$client->logout();
}

$result = $this->dkimValidator->validate($fullText);
Expand Down
4 changes: 2 additions & 2 deletions lib/Service/DraftsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public function saveMessage(Account $account, LocalMessage $message, array $to,
try {
$attachmentIds = $this->attachmentService->handleAttachments($account, $attachments, $client);
} finally {
$client->logout();
//$client->logout();
}

$message->setAttachments($this->attachmentService->saveLocalMessageAttachments($account->getUserId(), $message->getId(), $attachmentIds));
Expand Down Expand Up @@ -150,7 +150,7 @@ public function updateMessage(Account $account, LocalMessage $message, array $to
try {
$attachmentIds = $this->attachmentService->handleAttachments($account, $attachments, $client);
} finally {
$client->logout();
//$client->logout();
}
$message->setAttachments($this->attachmentService->updateLocalMessageAttachments($account->getUserId(), $message, $attachmentIds));
return $message;
Expand Down
Loading
Loading