PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.19
ووسلام – همگام سازی ووکامرس و باسلام v1.10.19
1.10.19 1.10.20 1.10.18 1.10.17 1.10.15 1.10.14 1.10.13 1.10.12 1.10.10 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.2 1.9.1 1.9.0 1.8.8 1.8.5 1.8.6 All 53 releases
← All changes | JobsRunner.php +43 -28 1.10.141.10.19 View file →
@@ -10,9 +10,12 @@
10 10 class JobsRunner
11 11 {
12 12 private const ASYNC_ACTION = 'sync_basalam_run_jobs_async';
13 13 private const ASYNC_DISPATCH_LOCK_TRANSIENT = 'sync_basalam_jobs_runner_async_dispatch_lock';
14 - private const ASYNC_DISPATCH_LOCK_SECONDS = 1;
14 + // Keep the dispatch lease longer than a normal async batch. Without this,
15 + // every frontend request can boot another full WordPress AJAX worker while
16 + // a large product queue is active.
17 + private const ASYNC_DISPATCH_LOCK_SECONDS = 25;
15 18 private const ASYNC_TIME_LIMIT_SECONDS = 20;
16 19 private const GLOBAL_RUNNER_LAST_RUN_OPTION = 'sync_basalam_jobs_runner_last_run';
17 20 private const STALE_PROCESSING_TIMEOUT_SECONDS = 120;
18 21
@@ -28,11 +31,11 @@
28 31 $CheckHttpBlockService
29 32 ) {
30 33 add_action('wp_ajax_' . self::ASYNC_ACTION, [$this, 'handleAsyncRequest']);
31 34 add_action('wp_ajax_nopriv_' . self::ASYNC_ACTION, [$this, 'handleAsyncRequest']);
32 - // Keep database work out of shutdown, where another callback may have
33 - // left the shared mysqli connection with an unread result set.
34 - add_action('init', [$this, 'maybeDispatchAsyncRequest'], 10, 0);
35 + // Probe and dispatch after the response path so normal storefront
36 + // requests never pay for the queue query or loopback HTTP request.
37 + add_action('shutdown', [$this, 'maybeDispatchAsyncRequest'], PHP_INT_MAX);
35 38 add_action('sync_basalam_job_created', [$this, 'maybeDispatchAsyncRequest'], 10, 0);
36 39
37 40 $this->jobManager = $jobManager;
38 41 $this->jobExecutor = $jobExecutor;
@@ -41,19 +44,15 @@
41 44 }
42 45
43 46 public function maybeDispatchAsyncRequest(): void
44 47 {
45 - // Retain a guard for third-party callers that may still invoke this
46 - // method during shutdown even though no shutdown hook is registered.
47 - if (function_exists('did_action') && did_action('shutdown')) return;
48 48 if ($this->isCurrentAsyncRequest()) return;
49 49 if ($this->CheckHttpBlockService->SyncBasalamHttpBlock()) return;
50 50 if (get_transient(self::ASYNC_DISPATCH_LOCK_TRANSIENT)) return;
51 51
52 - if (!$this->jobManager->hasPendingOrStaleProcessingJobs(self::STALE_PROCESSING_TIMEOUT_SECONDS)) {
53 - return;
54 - }
55 -
52 + // Reserve the dispatch lease before probing the queue. This keeps
53 + // concurrent shutdown callbacks from all running the queue query and
54 + // dispatching duplicate async workers.
56 55 set_transient(
57 56 self::ASYNC_DISPATCH_LOCK_TRANSIENT,
58 57 1,
59 58 self::ASYNC_DISPATCH_LOCK_SECONDS
@@ -58,8 +57,12 @@
58 57 1,
59 58 self::ASYNC_DISPATCH_LOCK_SECONDS
60 59 );
61 60
61 + if (!$this->jobManager->hasPendingOrStaleProcessingJobs(self::STALE_PROCESSING_TIMEOUT_SECONDS)) {
62 + return;
63 + }
64 +
62 65 $this->dispatchAsyncRequest();
63 66 }
64 67
65 68 public function handleAsyncRequest(): void
@@ -94,8 +97,16 @@
94 97 }
95 98
96 99 private function runAsyncBatch(): int
97 100 {
101 + if ($this->CheckHttpBlockService->SyncBasalamHttpBlock()) return 0;
102 +
103 + // Hold the advisory lock for the whole batch, including rate-limit
104 + // waits. Previously it was released after every job, so duplicate
105 + // async requests could pile up and sleep in parallel until the next
106 + // job became eligible, exhausting the site's PHP workers.
107 + if (!$this->jobExecutor->acquireGlobalJobsLock(0)) return 0;
108 +
98 109 $processed = 0;
99 110 $deadline = microtime(true) + (float) apply_filters(
100 111 'sync_basalam_jobs_runner_async_time_limit',
101 112 self::ASYNC_TIME_LIMIT_SECONDS
@@ -100,30 +111,34 @@
100 111 'sync_basalam_jobs_runner_async_time_limit',
101 112 self::ASYNC_TIME_LIMIT_SECONDS
102 113 );
103 114
104 - while (microtime(true) < $deadline) {
105 - if (!$this->jobManager->hasPendingOrStaleProcessingJobs(self::STALE_PROCESSING_TIMEOUT_SECONDS)) {
106 - break;
107 - }
115 + try {
116 + while (microtime(true) < $deadline) {
117 + if (!$this->jobManager->hasPendingOrStaleProcessingJobs(self::STALE_PROCESSING_TIMEOUT_SECONDS)) {
118 + break;
119 + }
108 120
109 - $ranJob = $this->checkAndRunJobs();
121 + $ranJob = $this->runEligibleJobs();
110 122
111 - if ($ranJob) {
112 - $processed++;
113 - }
123 + if ($ranJob) {
124 + $processed++;
125 + }
114 126
115 - $delay = $this->secondsUntilNextAllowedRun();
116 - if ($delay <= 0.0) {
117 - if (!$ranJob) break;
118 - continue;
119 - }
127 + $delay = $this->secondsUntilNextAllowedRun();
128 + if ($delay <= 0.0) {
129 + if (!$ranJob) break;
130 + continue;
131 + }
120 132
121 - if ((microtime(true) + $delay) >= $deadline) {
122 - break;
133 + if ((microtime(true) + $delay) >= $deadline) {
134 + break;
135 + }
136 +
137 + usleep((int) ($delay * 1000000));
123 138 }
124 -
125 - usleep((int) ($delay * 1000000));
139 + } finally {
140 + $this->jobExecutor->releaseGlobalJobsLock();
126 141 }
127 142
128 143 return $processed;
129 144 }