PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / Cron / CliCommands / ClaimedTaskRunner.php
mailpoet / lib / Cron / CliCommands Last commit date
ClaimedTaskRunner.php 1 month ago Cli.php 1 month ago CronCommand.php 1 month ago DaemonRunner.php 1 month ago ExecutionLimitOverride.php 1 month ago ScheduledTaskResolver.php 1 month ago ScheduledTasksLister.php 1 month ago TaskAdder.php 1 month ago TaskCanceller.php 1 month ago TaskRunner.php 1 month ago TaskTrigger.php 1 month ago WorkerTypesCatalog.php 1 month ago index.php 1 month ago
ClaimedTaskRunner.php
174 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Cron\CliCommands;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Cron\CronHelper;
9 use MailPoet\Cron\CronWorkerInterface;
10 use MailPoet\Entities\ScheduledTaskEntity;
11 use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
12 use MailPoetVendor\Carbon\Carbon;
13 use RuntimeException;
14 use Throwable;
15
16 /**
17 * Claims a single scheduled-task row for a WP-CLI process and drives it through one worker run.
18 *
19 * A claimed row carries status = STATUS_CLI, which is invisible to every daemon-side query, so the web
20 * daemon can neither pick it up nor reschedule it while the CLI run owns it. On success the row is
21 * completed (with a meta.cli breadcrumb merged after the worker's last write); on a partial run or
22 * failure it is handed back to the site cron (scheduled, due now); if requirements are not met it is
23 * removed.
24 *
25 * See doc/wp-cli-cron-commands.md ("CLI execution: the cli status") for the full rationale — why a
26 * dedicated status, the meta/inProgress placement, and how hard-killed (zombie) claims are handled.
27 */
28 class ClaimedTaskRunner {
29 private ScheduledTasksRepository $scheduledTasksRepository;
30
31 private ExecutionLimitOverride $executionLimitOverride;
32
33 public function __construct(
34 ScheduledTasksRepository $scheduledTasksRepository,
35 ExecutionLimitOverride $executionLimitOverride
36 ) {
37 $this->scheduledTasksRepository = $scheduledTasksRepository;
38 $this->executionLimitOverride = $executionLimitOverride;
39 }
40
41 /**
42 * Creates a fresh row already claimed (status STATUS_CLI), due now, and flushes it. No meta and no
43 * inProgress: the status alone hides the row, and workers may overwrite meta mid-run (see the doc).
44 * Public so a test can inspect the DB state after the claim.
45 */
46 public function claimNew(string $type, int $priority = ScheduledTaskEntity::PRIORITY_LOW): ScheduledTaskEntity {
47 $task = new ScheduledTaskEntity();
48 $task->setType($type);
49 $task->setStatus(ScheduledTaskEntity::STATUS_CLI);
50 $task->setPriority($priority);
51 $task->setScheduledAt(Carbon::now()->millisecond(0));
52 $this->scheduledTasksRepository->persist($task);
53 $this->scheduledTasksRepository->flush();
54 return $task;
55 }
56
57 /**
58 * Atomically claims an EXISTING row (scheduled/paused, already resolved by the caller) by transitioning
59 * it to STATUS_CLI in a single guarded UPDATE. The row's meta is preserved; only the status changes.
60 * Returns false when the row was no longer claimable (a concurrent CLI run or the site daemon got it
61 * first), so the caller can skip it instead of double-processing.
62 */
63 public function claimExisting(ScheduledTaskEntity $task): bool {
64 return $this->scheduledTasksRepository->claimAsCli($task);
65 }
66
67 /**
68 * Drives one already-claimed row through a single worker run. The claim is always resolved on a
69 * non-completing path so the row never stays in 'cli' unless the process dies: on success it is
70 * completed; on partial work, a failed prepare, a hit execution limit, or an error it is handed back to
71 * the site cron; when requirements are not met it is removed. Public so a test can drive a fake worker
72 * through each path directly.
73 *
74 * $timeout caps the worker's own execution-limit checks (in seconds); null lifts the cap so the worker
75 * runs to completion. A worker that hits the cap is handed back gracefully and reported via
76 * limit_reached rather than as a failure.
77 *
78 * @return array{completed: bool, limit_reached: bool, message: string}
79 */
80 public function run(CronWorkerInterface $worker, ScheduledTaskEntity $task, ?int $timeout = null): array {
81 // Capture the claim marker at the start of the run. Claim and run always happen back-to-back on
82 // the same instance, so this is the pid/started_at of the run that owns the row.
83 $marker = [
84 'pid' => getmypid(),
85 'started_at' => Carbon::now()->toIso8601String(),
86 ];
87
88 $requirementsMet = false;
89 try {
90 $requirementsMet = $worker->checkProcessingRequirements();
91 } catch (Throwable $e) {
92 $this->handBack($task);
93 throw new RuntimeException(sprintf("Task %d (%s) failed while running: %s. It was handed back to the site cron to retry.", $task->getId(), $worker->getTaskType(), $e->getMessage()), 0, $e);
94 }
95
96 if (!$requirementsMet) {
97 // Requirements not met: the claimed row would never run, so drop it rather than leave a stuck
98 // 'cli' task. Mirrors CronWorkerRunner, which removes such tasks.
99 $this->scheduledTasksRepository->remove($task);
100 $this->scheduledTasksRepository->flush();
101 throw new RuntimeException(sprintf("Requirements for '%s' are not met; the claimed task was removed and nothing ran.", $worker->getTaskType()));
102 }
103
104 $completed = false;
105 try {
106 $worker->init();
107
108 $this->executionLimitOverride->overrideDuring($timeout, function () use ($worker, $task, &$completed): void {
109 // Mirror CronWorkerRunner: a task is only processed after prepare succeeds. Bounce is the one
110 // standard worker that overrides prepare (it builds subscriber rows and returns false when there
111 // is nothing to do); when prepare returns false we leave $completed false and hand the row back.
112 if (!$worker->prepareTaskStrategy($task, microtime(true))) {
113 return;
114 }
115 $completed = (bool)$worker->processTaskStrategy($task, microtime(true));
116 });
117 } catch (Throwable $e) {
118 $this->handBack($task);
119 // A worker that hit the execution limit (when $timeout caps the run) is yielding, not failing:
120 // hand it back so the site cron continues it, and report it as limit_reached rather than an error.
121 if ($e->getCode() === CronHelper::DAEMON_EXECUTION_LIMIT_REACHED) {
122 return [
123 'completed' => false,
124 'limit_reached' => true,
125 'message' => sprintf('Task %d hit the execution limit; it was handed back to the site cron to continue.', $task->getId()),
126 ];
127 }
128 throw new RuntimeException(sprintf("Task %d (%s) failed while running: %s. It was handed back to the site cron to retry.", $task->getId(), $worker->getTaskType(), $e->getMessage()), 0, $e);
129 }
130
131 if ($completed) {
132 $this->complete($task, $marker);
133 return [
134 'completed' => true,
135 'limit_reached' => false,
136 'message' => sprintf('Task %d completed.', $task->getId()),
137 ];
138 }
139
140 $this->handBack($task);
141 return [
142 'completed' => false,
143 'limit_reached' => false,
144 'message' => sprintf('Task %d processed partially; it was handed back to the site cron to continue.', $task->getId()),
145 ];
146 }
147
148 /**
149 * Marks the row completed (STATUS_COMPLETED + processedAt) and stamps meta.cli as the permanent
150 * "done by CLI" breadcrumb. The merge happens AFTER the worker's last write, so a worker that
151 * overwrote meta wholesale mid-run cannot clobber the breadcrumb.
152 *
153 * @param array{pid: int|false, started_at: string} $marker
154 */
155 private function complete(ScheduledTaskEntity $task, array $marker): void {
156 $task->setProcessedAt(Carbon::now()->millisecond(0));
157 $task->setStatus(ScheduledTaskEntity::STATUS_COMPLETED);
158 $task->setMeta(array_merge($task->getMeta() ?? [], ['cli' => $marker]));
159 $this->scheduledTasksRepository->persist($task);
160 $this->scheduledTasksRepository->flush();
161 }
162
163 /**
164 * Hands a not-finished row back to the site cron: STATUS_SCHEDULED, due now. No meta.cli is written
165 * — the breadcrumb is only for tasks the CLI actually completed.
166 */
167 private function handBack(ScheduledTaskEntity $task): void {
168 $task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
169 $task->setScheduledAt(Carbon::now()->millisecond(0));
170 $this->scheduledTasksRepository->persist($task);
171 $this->scheduledTasksRepository->flush();
172 }
173 }
174