From a40fb36bb25e282b7777003e1d8630e2db36103e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Sun, 1 Dec 2024 16:29:27 +0100 Subject: [PATCH] First draft of background worker --- assets/db/create.sql | 2 +- commands/BackgroundJobController.php | 4 +- components/BackgroundJobProcessor.php | 35 ++++-- components/BackgroundJobScheduler.php | 23 ++++ components/mail/Tools.php | 94 +++------------ migrations/m241201_100317_background_jobs.php | 2 +- models/backgroundJobs/IBackgroundJob.php | 80 +++++++++++++ models/backgroundJobs/SendNotification.php | 112 ++++++++++++++++++ 8 files changed, 263 insertions(+), 89 deletions(-) create mode 100644 components/BackgroundJobScheduler.php create mode 100644 models/backgroundJobs/IBackgroundJob.php create mode 100644 models/backgroundJobs/SendNotification.php diff --git a/assets/db/create.sql b/assets/db/create.sql index b145bfab8f..6dac9e4fe1 100644 --- a/assets/db/create.sql +++ b/assets/db/create.sql @@ -828,7 +828,7 @@ CREATE TABLE `###TABLE_PREFIX###backgroundJob` ( `dateStarted` timestamp NULL DEFAULT NULL, `dateUpdated` timestamp NULL DEFAULT NULL, `dateFinished` timestamp NULL DEFAULT NULL, - `payload` mediumblob NOT NULL + `payload` mediumtext NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- diff --git a/commands/BackgroundJobController.php b/commands/BackgroundJobController.php index 3daf65c222..29dbab5568 100644 --- a/commands/BackgroundJobController.php +++ b/commands/BackgroundJobController.php @@ -32,10 +32,8 @@ public function actionRun(): void if ($row) { $processor->processRow($row); } else { - usleep(100000); + usleep(100_000); } - - file_put_contents('/tmp/memory_usage.log', date("Y-m-d H:i:s") . ": " . memory_get_peak_usage() . "\n", FILE_APPEND); } } diff --git a/components/BackgroundJobProcessor.php b/components/BackgroundJobProcessor.php index aeb98bb76c..b977f500e7 100644 --- a/components/BackgroundJobProcessor.php +++ b/components/BackgroundJobProcessor.php @@ -4,6 +4,7 @@ namespace app\components; +use app\models\backgroundJobs\IBackgroundJob; use yii\db\Connection; class BackgroundJobProcessor @@ -18,10 +19,10 @@ public function __construct(Connection $connection) { $this->startedAt = new \DateTimeImmutable(); } - public function getJobAndSetStarted(): ?array { - $foundRow = null; + public function getJobAndSetStarted(): ?IBackgroundJob { + $foundJob = null; - $this->connection->transaction(function () use (&$foundRow) { + $this->connection->transaction(function () use (&$foundJob) { $command = $this->connection->createCommand('SELECT * FROM backgroundJob WHERE dateStarted IS NULL ORDER BY id ASC LIMIT 0,1 FOR UPDATE'); $foundRows = $command->queryAll(); if (empty($foundRows)) { @@ -30,16 +31,34 @@ public function getJobAndSetStarted(): ?array { $foundRow = $foundRows[0]; $this->connection->createCommand('UPDATE backgroundJob SET dateStarted = NOW() WHERE id = :id', ['id' => $foundRow['id']])->execute(); + + $foundJob = IBackgroundJob::fromJson( + intval($foundRow['id']), + $foundRow['type'], + ($foundRow['siteId'] > 0 ? $foundRow['siteId'] : null), + ($foundRow['consultationId'] > 0 ? $foundRow['consultationId'] : null), + $foundRow['payload'] + ); }); - return $foundRow; + return $foundJob; } - public function processRow(array $row): void + public function processRow(IBackgroundJob $job): void { - echo "Processing row: " . $row['id'] . "\n"; - sleep(2); - $this->connection->createCommand('UPDATE backgroundJob SET dateFinished = NOW() WHERE id = :id', ['id' => $row['id']])->execute(); + echo "Processing row: " . $job->getId() . "\n"; + + $this->connection->createCommand( + 'UPDATE backgroundJob SET dateUpdated = NOW() WHERE id = :id', + ['id' => $job->getId()] + )->execute(); + + $job->execute(); + + $this->connection->createCommand( + 'UPDATE backgroundJob SET dateFinished = NOW() WHERE id = :id', + ['id' => $job->getId()] + )->execute(); } public function getProcessedEvents(): int { diff --git a/components/BackgroundJobScheduler.php b/components/BackgroundJobScheduler.php new file mode 100644 index 0000000000..b24ceb2fd0 --- /dev/null +++ b/components/BackgroundJobScheduler.php @@ -0,0 +1,23 @@ +getDb()->createCommand( + 'INSERT INTO backgroundJob (`siteId`, `consultationId`, `type`, `dateCreation`, `payload`) VALUES (:siteId, :consultationId, :type, NOW(), :payload)', + [ + ':siteId' => $job->getSite()?->id, + ':consultationId' => $job->getConsultation()?->id, + ':type' => $job->getTypeId(), + ':payload' => $job->toJson(), + ] + )->execute(); + } +} diff --git a/components/mail/Tools.php b/components/mail/Tools.php index f568d1ccf7..e8fc8b8ff5 100644 --- a/components/mail/Tools.php +++ b/components/mail/Tools.php @@ -4,11 +4,10 @@ namespace app\components\mail; -use app\components\RequestContext; +use app\components\BackgroundJobScheduler; +use app\models\backgroundJobs\SendNotification; use app\models\settings\AntragsgruenApp; -use app\models\db\{Consultation, EMailLog, IMotion, User}; -use app\models\exceptions\{MailNotSent, ServerConfiguration}; -use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use app\models\db\{Consultation, IMotion, User}; class Tools { @@ -53,10 +52,6 @@ public static function getDefaultReplyTo(?IMotion $imotion = null, ?Consultation return $replyTo; } - /** - * @throws MailNotSent - * @throws ServerConfiguration - */ public static function sendWithLog( int $mailType, ?Consultation $fromConsultation, @@ -70,79 +65,26 @@ public static function sendWithLog( ?string $replyTo = null ): void { $params = AntragsgruenApp::getInstance(); - $mailer = Base::createMailer($params->mailService); - if (!$mailer) { - throw new MailNotSent('E-Mail not configured'); - } - - $sendTextPlain = ($noLogReplaces ? str_replace( - array_keys($noLogReplaces), - array_values($noLogReplaces), - $textPlain - ) : $textPlain); - $sendTextHtml = ($noLogReplaces ? str_replace( - array_keys($noLogReplaces), - array_values($noLogReplaces), - $textHtml - ) : $textHtml); - $fromEmail = $params->mailFromEmail; if (!$fromName) { - $fromName = static::getDefaultMailFromName($fromConsultation); + $fromName = Tools::getDefaultMailFromName($fromConsultation); } if (!$replyTo) { - $replyTo = static::getDefaultReplyTo(null, $fromConsultation); - } - - $exception = null; - $messageId = ''; - try { - $message = $mailer->createMessage( - $subject, - $sendTextPlain, - $sendTextHtml, - $fromName, - $fromEmail, - $replyTo, - $fromConsultation - ); - $result = $mailer->send($message, $toEmail); - if (is_string($result)) { - $status = EMailLog::STATUS_SENT; - $messageId = $result; - } else { - $status = $result; - } - } catch (TransportExceptionInterface $e) { - $status = EMailLog::STATUS_DELIVERY_ERROR; - $exception = $e; + $replyTo = Tools::getDefaultReplyTo(null, $fromConsultation); } - $obj = new EMailLog(); - if ($toPersonId) { - $obj->toUserId = $toPersonId; - } - if ($fromConsultation) { - $obj->fromSiteId = $fromConsultation->siteId; - } - $obj->toEmail = $toEmail; - $obj->type = $mailType; - $obj->fromEmail = $fromName . ' <' . $fromEmail . '>'; - $obj->subject = mb_substr($subject, 0, 190); - $obj->text = $textPlain; - $obj->dateSent = date('Y-m-d H:i:s'); - $obj->status = $status; - $obj->messageId = $messageId; - $obj->save(); - - if ($exception) { - \Yii::error($exception->getMessage()); - throw new MailNotSent($exception->getMessage()); - } - - if (YII_ENV === 'test') { - $pre = RequestContext::getSession()->getFlash('email', ''); - RequestContext::getSession()->setFlash('email', $pre . 'E-Mail sent to: ' . $toEmail . " (Type $mailType)\n"); - } + BackgroundJobScheduler::executeOrScheduleJob(new SendNotification( + $fromConsultation, + $mailType, + $toEmail, + $toPersonId, + $subject, + $textPlain, + $textHtml, + $noLogReplaces, + $fromEmail, + $fromName, + $replyTo + )); } } diff --git a/migrations/m241201_100317_background_jobs.php b/migrations/m241201_100317_background_jobs.php index 214827ff71..2919893bb2 100644 --- a/migrations/m241201_100317_background_jobs.php +++ b/migrations/m241201_100317_background_jobs.php @@ -18,7 +18,7 @@ public function safeUp(): void 'dateStarted' => 'TIMESTAMP DEFAULT NULL', 'dateUpdated' => 'TIMESTAMP DEFAULT NULL', 'dateFinished' => 'TIMESTAMP DEFAULT NULL', - 'payload' => 'MEDIUMBLOB NOT NULL', + 'payload' => 'MEDIUMTEXT NOT NULL', ]); $this->addForeignKey('fk_background_site', 'backgroundJob', 'siteId', 'site', 'id'); diff --git a/models/backgroundJobs/IBackgroundJob.php b/models/backgroundJobs/IBackgroundJob.php new file mode 100644 index 0000000000..47d17fb4eb --- /dev/null +++ b/models/backgroundJobs/IBackgroundJob.php @@ -0,0 +1,80 @@ +> + */ + public static function getAllBackgroundJobs(): array + { + return [ + SendNotification::TYPE_ID => SendNotification::class, + ]; + } + + public function toJson(): string + { + $serializer = Tools::getSerializer(); + + return $serializer->serialize($this, 'json'); + } + + /** + * @Ignore + */ + public function getConsultation(): ?Consultation + { + return $this->consultation; + } + + /** + * @Ignore + */ + public function getSite(): ?Site + { + return $this->site; + } + + /** + * @Ignore + */ + public function getId(): ?int + { + return $this->id; + } + + public static function fromJson(int $id, string $typeId, ?int $siteId, ?int $consultationId, string $json): IBackgroundJob + { + $serializer = Tools::getSerializer(); + + $class = self::getAllBackgroundJobs()[$typeId]; + + /** @var IBackgroundJob $job */ + $job = $serializer->deserialize($json, $class, 'json'); + $job->id = $id; + if ($siteId !== null) { + $job->site = Site::findOne(['id' => $siteId]); + } + if ($consultationId !== null) { + $job->consultation = Consultation::findOne(['id' => $consultationId]); + } + + return $job; + } +} diff --git a/models/backgroundJobs/SendNotification.php b/models/backgroundJobs/SendNotification.php new file mode 100644 index 0000000000..44a382517a --- /dev/null +++ b/models/backgroundJobs/SendNotification.php @@ -0,0 +1,112 @@ +consultation = $consultation; + } + + public function getTypeId(): string + { + return self::TYPE_ID; + } + + public function execute(): void + { + file_put_contents('/tmp/sendmail.log', 'SEND MAIL: ' . $this->toEmail . PHP_EOL, FILE_APPEND); + + $params = AntragsgruenApp::getInstance(); + $mailer = Base::createMailer($params->mailService); + if (!$mailer) { + throw new MailNotSent('E-Mail not configured'); + } + + $sendTextPlain = ($this->noLogReplaces ? str_replace( + array_keys($this->noLogReplaces), + array_values($this->noLogReplaces), + $this->textPlain + ) : $this->textPlain); + $sendTextHtml = ($this->noLogReplaces ? str_replace( + array_keys($this->noLogReplaces), + array_values($this->noLogReplaces), + $this->textHtml + ) : $this->textHtml); + + $exception = null; + $messageId = ''; + try { + $message = $mailer->createMessage( + $this->subject, + $sendTextPlain, + $sendTextHtml, + $this->fromName, + $this->fromEmail, + $this->replyTo, + $this->consultation + ); + $result = $mailer->send($message, $this->toEmail); + if (is_string($result)) { + $status = EMailLog::STATUS_SENT; + $messageId = $result; + } else { + $status = $result; + } + } catch (TransportExceptionInterface $e) { + $status = EMailLog::STATUS_DELIVERY_ERROR; + $exception = $e; + } + + $obj = new EMailLog(); + if ($this->toPersonId) { + $obj->toUserId = $this->toPersonId; + } + if ($this->consultation) { + $obj->fromSiteId = $this->consultation->siteId; + } + $obj->toEmail = $this->toEmail; + $obj->type = $this->mailType; + $obj->fromEmail = $this->fromName . ' <' . $this->fromEmail . '>'; + $obj->subject = mb_substr($this->subject, 0, 190); + $obj->text = $this->textPlain; + $obj->dateSent = date('Y-m-d H:i:s'); + $obj->status = $status; + $obj->messageId = $messageId; + $obj->save(); + + if ($exception) { + \Yii::error($exception->getMessage()); + throw new MailNotSent($exception->getMessage()); + } + + if (YII_ENV === 'test') { + $pre = RequestContext::getSession()->getFlash('email', ''); + RequestContext::getSession()->setFlash('email', $pre . 'E-Mail sent to: ' . $this->toEmail . " (Type $this->mailType)\n"); + } + } +}