Compare commits

...

4 Commits

Author SHA1 Message Date
0fe57f61e6 Release 3.100.1 2022-10-06 13:07:26 +03:00
1ed669da01 Delete task when no newsletter was found
When no corresponding newsletter was found the task can be deleted. The behavior
before meant that such a task would remain forever in the database with the
status 'running'.

[MAILPOET-4699]
2022-10-06 12:30:05 +03:00
86ddf98b7c Allow newsletter_id to be NULL in sending_queue table
[MAILPOET-4699]
2022-10-06 12:19:31 +03:00
cb575f1972 Improve logging of errors in cron
Sometimes errors overlap each other so that the 'Last Seen error' we log
does not provide sufficient information on the cause of a problem.

E.g. one worker might cause an SQL issue, which closes the EntityManager and a
subsequent worker might overwrite this error with the information that the
EntityManager is closed.

With this commit, both those issues will be logged as an error.

[MAILPOET-4699]
2022-10-06 12:19:25 +03:00
9 changed files with 53 additions and 18 deletions

View File

@ -218,7 +218,7 @@ class Migrator {
$attributes = [ $attributes = [
'id int(11) unsigned NOT NULL AUTO_INCREMENT,', 'id int(11) unsigned NOT NULL AUTO_INCREMENT,',
'task_id int(11) unsigned NOT NULL,', 'task_id int(11) unsigned NOT NULL,',
'newsletter_id int(11) unsigned NOT NULL,', 'newsletter_id int(11) unsigned NULL,',
'newsletter_rendered_body longtext,', 'newsletter_rendered_body longtext,',
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,', 'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
'subscribers longtext,', 'subscribers longtext,',

View File

@ -3,6 +3,7 @@
namespace MailPoet\Cron; namespace MailPoet\Cron;
use MailPoet\Entities\ScheduledTaskEntity; use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Logging\LoggerFactory;
use MailPoet\Newsletter\Sending\ScheduledTasksRepository; use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\Carbon\Carbon; use MailPoetVendor\Carbon\Carbon;
@ -27,17 +28,22 @@ class CronWorkerRunner {
/** @var ScheduledTasksRepository */ /** @var ScheduledTasksRepository */
private $scheduledTasksRepository; private $scheduledTasksRepository;
/** @var LoggerFactory */
private $loggerFactory;
public function __construct( public function __construct(
CronHelper $cronHelper, CronHelper $cronHelper,
CronWorkerScheduler $cronWorkerScheduler, CronWorkerScheduler $cronWorkerScheduler,
WPFunctions $wp, WPFunctions $wp,
ScheduledTasksRepository $scheduledTasksRepository ScheduledTasksRepository $scheduledTasksRepository,
LoggerFactory $loggerFactory
) { ) {
$this->timer = microtime(true); $this->timer = microtime(true);
$this->cronHelper = $cronHelper; $this->cronHelper = $cronHelper;
$this->cronWorkerScheduler = $cronWorkerScheduler; $this->cronWorkerScheduler = $cronWorkerScheduler;
$this->wp = $wp; $this->wp = $wp;
$this->scheduledTasksRepository = $scheduledTasksRepository; $this->scheduledTasksRepository = $scheduledTasksRepository;
$this->loggerFactory = $loggerFactory;
} }
public function run(CronWorkerInterface $worker) { public function run(CronWorkerInterface $worker) {
@ -72,6 +78,10 @@ class CronWorkerRunner {
} }
} catch (\Exception $e) { } catch (\Exception $e) {
if (isset($task) && $task && $e->getCode() !== CronHelper::DAEMON_EXECUTION_LIMIT_REACHED) { if (isset($task) && $task && $e->getCode() !== CronHelper::DAEMON_EXECUTION_LIMIT_REACHED) {
/**
* ToDo: Use \LoggerFactory::TOPIC_CRON as logger topic, once it is available
*/
$this->loggerFactory->getLogger()->error($e->getMessage(), ['error' => $e]);
$this->cronWorkerScheduler->rescheduleProgressively($task); $this->cronWorkerScheduler->rescheduleProgressively($task);
} }
throw $e; throw $e;

View File

@ -3,6 +3,7 @@
namespace MailPoet\Cron; namespace MailPoet\Cron;
use MailPoet\Cron\Workers\WorkersFactory; use MailPoet\Cron\Workers\WorkersFactory;
use MailPoet\Logging\LoggerFactory;
class Daemon { class Daemon {
public $timer; public $timer;
@ -16,15 +17,20 @@ class Daemon {
/** @var WorkersFactory */ /** @var WorkersFactory */
private $workersFactory; private $workersFactory;
/** @var LoggerFactory */
private $loggerFactory;
public function __construct( public function __construct(
CronHelper $cronHelper, CronHelper $cronHelper,
CronWorkerRunner $cronWorkerRunner, CronWorkerRunner $cronWorkerRunner,
WorkersFactory $workersFactory WorkersFactory $workersFactory,
LoggerFactory $loggerFactory
) { ) {
$this->timer = microtime(true); $this->timer = microtime(true);
$this->workersFactory = $workersFactory; $this->workersFactory = $workersFactory;
$this->cronWorkerRunner = $cronWorkerRunner; $this->cronWorkerRunner = $cronWorkerRunner;
$this->cronHelper = $cronHelper; $this->cronHelper = $cronHelper;
$this->loggerFactory = $loggerFactory;
} }
public function run($settingsDaemonData) { public function run($settingsDaemonData) {
@ -41,10 +47,16 @@ class Daemon {
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$workerClassNameParts = explode('\\', get_class($worker)); $workerClassNameParts = explode('\\', get_class($worker));
$workerName = end($workerClassNameParts);
$errors[] = [ $errors[] = [
'worker' => end($workerClassNameParts), 'worker' => $workerName,
'message' => $e->getMessage(), 'message' => $e->getMessage(),
]; ];
/**
* ToDo: Use \LoggerFactory::TOPIC_CRON as logger topic, once it is available
*/
$this->loggerFactory->getLogger()->error($e->getMessage(), ['error' => $e, 'worker' => $workerName]);
} }
} }

View File

@ -156,22 +156,20 @@ class SendingQueue {
$newsletterEntity = $this->newsletterTask->getNewsletterFromQueue($queue); $newsletterEntity = $this->newsletterTask->getNewsletterFromQueue($queue);
if (!$newsletterEntity) { if (!$newsletterEntity) {
$this->deleteTask($queue);
return; return;
} }
$newsletter = Newsletter::findOne($newsletterEntity->getId()); $newsletter = Newsletter::findOne($newsletterEntity->getId());
if (!$newsletter) { if (!$newsletter) {
$this->deleteTask($queue);
return; return;
} }
// pre-process newsletter (render, replace shortcodes/links, etc.) // pre-process newsletter (render, replace shortcodes/links, etc.)
$newsletter = $this->newsletterTask->preProcessNewsletter($newsletter, $queue); $newsletter = $this->newsletterTask->preProcessNewsletter($newsletter, $queue);
if (!$newsletter) { if (!$newsletter) {
$this->loggerFactory->getLogger(LoggerFactory::TOPIC_NEWSLETTERS)->info( $this->deleteTask($queue);
'delete task in sending queue',
['task_id' => $queue->taskId]
);
$queue->delete();
return; return;
} }
// clone the original object to be used for processing // clone the original object to be used for processing
@ -264,6 +262,14 @@ class SendingQueue {
} }
} }
private function deleteTask(SendingTask $queue) {
$this->loggerFactory->getLogger(LoggerFactory::TOPIC_NEWSLETTERS)->info(
'delete task in sending queue',
['task_id' => $queue->taskId]
);
$queue->delete();
}
public function getBatchSize(): int { public function getBatchSize(): int {
return $this->throttlingHandler->getBatchSize(); return $this->throttlingHandler->getBatchSize();
} }

View File

@ -2,7 +2,7 @@
/* /*
* Plugin Name: MailPoet * Plugin Name: MailPoet
* Version: 3.100.0 * Version: 3.100.1
* Plugin URI: http://www.mailpoet.com * Plugin URI: http://www.mailpoet.com
* Description: Create and send newsletters, post notifications and welcome emails from your WordPress. * Description: Create and send newsletters, post notifications and welcome emails from your WordPress.
* Author: MailPoet * Author: MailPoet
@ -17,7 +17,7 @@
*/ */
$mailpoetPlugin = [ $mailpoetPlugin = [
'version' => '3.100.0', 'version' => '3.100.1',
'filename' => __FILE__, 'filename' => __FILE__,
'path' => dirname(__FILE__), 'path' => dirname(__FILE__),
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php', 'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',

View File

@ -3,7 +3,7 @@ Contributors: mailpoet
Tags: email, email marketing, post notification, woocommerce emails, email automation, newsletter, newsletter builder, newsletter subscribers Tags: email, email marketing, post notification, woocommerce emails, email automation, newsletter, newsletter builder, newsletter subscribers
Requires at least: 5.6 Requires at least: 5.6
Tested up to: 6.0 Tested up to: 6.0
Stable tag: 3.100.0 Stable tag: 3.100.1
Requires PHP: 7.2 Requires PHP: 7.2
License: GPLv3 License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -219,6 +219,9 @@ Check our [Knowledge Base](https://kb.mailpoet.com) or contact us through our [s
== Changelog == == Changelog ==
= 3.100.1 - 2022-10-06 =
* Fixed: In some instances the sending stuck because the EntityManager was closed.
= 3.100.0 - 2022-10-03 = = 3.100.0 - 2022-10-03 =
* Added: tagging subscribers when signed up via a form; * Added: tagging subscribers when signed up via a form;
* Improved: linux cron error message; * Improved: linux cron error message;

View File

@ -9,6 +9,7 @@ use MailPoet\Cron\CronWorkerRunner;
use MailPoet\Cron\CronWorkerScheduler; use MailPoet\Cron\CronWorkerScheduler;
use MailPoet\Cron\Workers\SimpleWorkerMockImplementation; use MailPoet\Cron\Workers\SimpleWorkerMockImplementation;
use MailPoet\Entities\ScheduledTaskEntity; use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Logging\LoggerFactory;
use MailPoet\Newsletter\Sending\ScheduledTasksRepository; use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory; use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
@ -33,7 +34,8 @@ class CronWorkerRunnerTest extends \MailPoetTest {
$this->cronHelper, $this->cronHelper,
$this->diContainer->get(CronWorkerScheduler::class), $this->diContainer->get(CronWorkerScheduler::class),
$this->diContainer->get(WPFunctions::class), $this->diContainer->get(WPFunctions::class),
$this->scheduledTasksRepository $this->scheduledTasksRepository,
$this->diContainer->get(LoggerFactory::class)
); );
} }

View File

@ -11,6 +11,7 @@ use MailPoet\Cron\DaemonHttpRunner;
use MailPoet\Cron\Triggers\WordPress; use MailPoet\Cron\Triggers\WordPress;
use MailPoet\Cron\Workers\SimpleWorker; use MailPoet\Cron\Workers\SimpleWorker;
use MailPoet\Cron\Workers\WorkersFactory; use MailPoet\Cron\Workers\WorkersFactory;
use MailPoet\Logging\LoggerFactory;
use MailPoet\Settings\SettingsController; use MailPoet\Settings\SettingsController;
use MailPoet\Settings\SettingsRepository; use MailPoet\Settings\SettingsRepository;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
@ -79,7 +80,7 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
->method('run') ->method('run')
->willThrowException(new \Exception()); ->willThrowException(new \Exception());
$daemon = new Daemon($this->cronHelper, $cronWorkerRunnerMock, $this->createWorkersFactoryMock()); $daemon = new Daemon($this->cronHelper, $cronWorkerRunnerMock, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
$daemonHttpRunner = $this->make(DaemonHttpRunner::class, [ $daemonHttpRunner = $this->make(DaemonHttpRunner::class, [
'pauseExecution' => null, 'pauseExecution' => null,
'callSelf' => null, 'callSelf' => null,
@ -205,7 +206,7 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
$cronWorkerRunner = $this->make(CronWorkerRunner::class, [ $cronWorkerRunner = $this->make(CronWorkerRunner::class, [
'run' => null, 'run' => null,
]); ]);
$daemon = new Daemon($this->cronHelper, $cronWorkerRunner, $this->createWorkersFactoryMock()); $daemon = new Daemon($this->cronHelper, $cronWorkerRunner, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
$daemonHttpRunner->__construct($daemon, $this->cronHelper, SettingsController::getInstance(), $this->diContainer->get(WordPress::class)); $daemonHttpRunner->__construct($daemon, $this->cronHelper, SettingsController::getInstance(), $this->diContainer->get(WordPress::class));
$daemonHttpRunner->run($data); $daemonHttpRunner->run($data);
$updatedDaemon = $this->settings->get(CronHelper::DAEMON_SETTING); $updatedDaemon = $this->settings->get(CronHelper::DAEMON_SETTING);
@ -225,7 +226,7 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
->method('run') ->method('run')
->willThrowException(new \Exception()); ->willThrowException(new \Exception());
$daemon = new Daemon($this->cronHelper, $cronWorkerRunnerMock, $this->createWorkersFactoryMock()); $daemon = new Daemon($this->cronHelper, $cronWorkerRunnerMock, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
$daemonHttpRunner = $this->make(DaemonHttpRunner::class, [ $daemonHttpRunner = $this->make(DaemonHttpRunner::class, [
'pauseExecution' => null, 'pauseExecution' => null,
'callSelf' => null, 'callSelf' => null,
@ -261,7 +262,7 @@ class DaemonHttpRunnerTest extends \MailPoetTest {
$cronWorkerRunnerMock = $this->make(CronWorkerRunner::class, [ $cronWorkerRunnerMock = $this->make(CronWorkerRunner::class, [
'run' => null, 'run' => null,
]); ]);
$daemon = new Daemon($this->cronHelper, $cronWorkerRunnerMock, $this->createWorkersFactoryMock()); $daemon = new Daemon($this->cronHelper, $cronWorkerRunnerMock, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
$daemonHttpRunner->__construct($daemon, $this->cronHelper, SettingsController::getInstance(), $this->diContainer->get(WordPress::class)); $daemonHttpRunner->__construct($daemon, $this->cronHelper, SettingsController::getInstance(), $this->diContainer->get(WordPress::class));
$daemonHttpRunner->run($data); $daemonHttpRunner->run($data);
expect(ignore_user_abort())->equals(true); expect(ignore_user_abort())->equals(true);

View File

@ -8,6 +8,7 @@ use MailPoet\Cron\CronWorkerRunner;
use MailPoet\Cron\Daemon; use MailPoet\Cron\Daemon;
use MailPoet\Cron\Workers\SimpleWorker; use MailPoet\Cron\Workers\SimpleWorker;
use MailPoet\Cron\Workers\WorkersFactory; use MailPoet\Cron\Workers\WorkersFactory;
use MailPoet\Logging\LoggerFactory;
use MailPoet\Settings\SettingsController; use MailPoet\Settings\SettingsController;
use MailPoet\Settings\SettingsRepository; use MailPoet\Settings\SettingsRepository;
@ -31,7 +32,7 @@ class DaemonTest extends \MailPoetTest {
'token' => 123, 'token' => 123,
]; ];
$this->settings->set(CronHelper::DAEMON_SETTING, $data); $this->settings->set(CronHelper::DAEMON_SETTING, $data);
$daemon = new Daemon($this->cronHelper, $cronWorkerRunner, $this->createWorkersFactoryMock()); $daemon = new Daemon($this->cronHelper, $cronWorkerRunner, $this->createWorkersFactoryMock(), $this->diContainer->get(LoggerFactory::class));
$daemon->run($data); $daemon->run($data);
} }