Skip to content

Fix: dedupe deferred wcpay_track_order scheduling before ActionScheduler init (WOOPMNT-3789)#11609

Open
ChrissiePollock wants to merge 1 commit intodevelopfrom
fix/WOOPMNT-3789-dedupe-wcpay-track-order-actions
Open

Fix: dedupe deferred wcpay_track_order scheduling before ActionScheduler init (WOOPMNT-3789)#11609
ChrissiePollock wants to merge 1 commit intodevelopfrom
fix/WOOPMNT-3789-dedupe-wcpay-track-order-actions

Conversation

@ChrissiePollock
Copy link
Copy Markdown

Fixes #8219

Linear ticket: WOOPMNT-3789

Changes proposed in this Pull Request

WC_Payments_Action_Scheduler_Service::schedule_job() previously registered a new anonymous closure on action_scheduler_init every time it was called while ActionScheduler was still uninitialized. schedule_order_tracking() is hooked to woocommerce_update_order, which fires multiple times during a single order-creation request — so each call queued its own closure, each with its own captured $timestamp (from strtotime('+5 seconds') at slightly different moments).

When action_scheduler_init then fires, every queued closure executes. Because the timestamps differ by fractions of a second, schedule_action_and_prevent_duplicates() cannot reliably collapse them: one closure unschedules and reschedules, then the next closure does the same thing with its own timestamp, and so on. The _new_order_tracking_complete meta guard doesn't help either, because it's set only after the tracking action runs, not when it's scheduled.

Result in the wild: ~350k wcpay_track_new_order actions scheduled across ~1300 orders, causing Action Scheduler table deadlocks and driving the DB server offline. A third-party plugin (LearnPress - WooCommerce Payment Methods Integration) was also observed looping through woocommerce_update_order, amplifying the issue.

This PR adds a $deferred_jobs property keyed by md5( hook + args + group ):

  • On the first deferred call for a given key, register one action_scheduler_init callback.
  • On every call (first or subsequent), update $deferred_jobs[$key] with the latest $timestamp.
  • When action_scheduler_init fires, the single callback reads the latest stored timestamp and hands it off to schedule_action_and_prevent_duplicates().

Net effect: at most one scheduled action per unique hook+args+group per request, regardless of how many times schedule_job() was called before ActionScheduler initialized. schedule_action_and_prevent_duplicates() is kept as a second safety net for the post-init path.

Minimal change — no new files, no architectural changes, just deduplication of the deferred closures.

Questions for PR author:

  • How can this code break? If two distinct callers legitimately need different timestamps for the same hook+args+group before AS init, only the last one wins. That's already the de-facto behaviour after schedule_action_and_prevent_duplicates() unschedules and reschedules, so this preserves existing semantics while avoiding the intermediate churn.
  • What are we doing to make sure this code doesn't break? The $key uses the same inputs that schedule_action_and_prevent_duplicates() uses to identify duplicates, so dedup logic is consistent across the pre-init and post-init paths.

Testing instructions

  1. On a test site running WC ≥ 7.9, install a plugin (or add a mu-plugins snippet) that calls do_action( 'woocommerce_update_order', $order_id ) several times during a single request — e.g. fire it 3× inside a woocommerce_new_order hook.
  2. Place a new order that routes through WooPayments.
  3. Go to WooCommerce > Status > Scheduled Actions and search for wcpay_track_new_order.
  4. Before: multiple pending/canceled actions per order. After: exactly one pending action per order.
  5. Let the action run (or trigger manually). Confirm the order gets tracked once and _new_order_tracking_complete is set.
  6. Regression checks:
    • A normal order (no amplifying plugin) still schedules exactly one tracking action.
    • Updating an existing order still schedules exactly one wcpay_track_update_order action.
    • On WC < 7.9 (no action_scheduler_init hook), the else-branch still runs schedule_action_and_prevent_duplicates() directly.

  • Run npm run changelog to add a changelog file, choose patch to leave it empty if the change is not significant. You can add multiple changelog files in one PR by running this command a few times.
  • Covered with tests (no existing unit tests for this class; happy to add one if the reviewer would like — the behaviour under test is "N calls before AS init → 1 registered callback → 1 scheduled action").
  • Tested on mobile (or does not apply) — backend-only change.

Post merge

…ler init

schedule_job() previously registered a new anonymous closure on
action_scheduler_init every time it was called before ActionScheduler had
initialized. When woocommerce_update_order fires multiple times during a
single order-creation request, each call queued its own closure with its
own captured timestamp. Because the timestamps differ by fractions of a
second, schedule_action_and_prevent_duplicates() cannot reliably collapse
them, so several wcpay_track_new_order actions end up scheduled per order.

On one site this produced ~350k scheduled actions for ~1300 orders and
caused Action Scheduler table deadlocks.

Track deferred jobs in $deferred_jobs keyed by hook+args+group so only one
action_scheduler_init callback is registered per unique combination. Repeat
calls just update the stored timestamp; the single callback reads the latest
value when ActionScheduler initializes, and schedule_action_and_prevent_duplicates()
remains as a second safety net.

Closes WOOPMNT-3789
@github-actions
Copy link
Copy Markdown
Contributor

Test the build

Option 1. Jetpack Beta

  • Install and activate Jetpack Beta.
  • Use this build by searching for PR number 11609 or branch name fix/WOOPMNT-3789-dedupe-wcpay-track-order-actions in your-test.site/wp-admin/admin.php?page=jetpack-beta&plugin=woocommerce-payments

Option 2. Jurassic Ninja - available for logged-in A12s

🚀 Launch a JN site with this branch 🚀

ℹ️ Install this Tampermonkey script to get more options.


Build info:

  • Latest commit: b664436
  • Build time: 2026-04-17 17:39:55 UTC

Note: the build is updated when a new commit is pushed to this PR.

@github-actions
Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 963 kB

ℹ️ View Unchanged
Filename Size
release/woocommerce-payments/assets/css/admin.css 1.46 kB
release/woocommerce-payments/assets/css/admin.rtl.css 1.46 kB
release/woocommerce-payments/assets/css/success.css 1.06 kB
release/woocommerce-payments/assets/css/success.rtl.css 1.06 kB
release/woocommerce-payments/dist/blocks-checkout-rtl.css 3.25 kB
release/woocommerce-payments/dist/blocks-checkout.css 3.25 kB
release/woocommerce-payments/dist/blocks-checkout.js 56.6 kB
release/woocommerce-payments/dist/cart-block-rtl.css 113 B
release/woocommerce-payments/dist/cart-block.css 112 B
release/woocommerce-payments/dist/cart-block.js 17.4 kB
release/woocommerce-payments/dist/cart.js 5.15 kB
release/woocommerce-payments/dist/checkout-rtl.css 1.01 kB
release/woocommerce-payments/dist/checkout.css 1.01 kB
release/woocommerce-payments/dist/checkout.js 34.9 kB
release/woocommerce-payments/dist/chunks/express-checkout-previews.js 3.52 kB
release/woocommerce-payments/dist/express-checkout-rtl.css 377 B
release/woocommerce-payments/dist/express-checkout.css 377 B
release/woocommerce-payments/dist/express-checkout.js 19.3 kB
release/woocommerce-payments/dist/frontend-tracks.js 868 B
release/woocommerce-payments/dist/index-rtl.css 21.5 kB
release/woocommerce-payments/dist/index.css 21.5 kB
release/woocommerce-payments/dist/index.js 152 kB
release/woocommerce-payments/dist/multi-currency-analytics.js 1.09 kB
release/woocommerce-payments/dist/multi-currency-async-renderer-rtl.css 344 B
release/woocommerce-payments/dist/multi-currency-async-renderer.css 344 B
release/woocommerce-payments/dist/multi-currency-async-renderer.js 8.1 kB
release/woocommerce-payments/dist/multi-currency-rtl.css 3.82 kB
release/woocommerce-payments/dist/multi-currency-switcher-block.js 19.2 kB
release/woocommerce-payments/dist/multi-currency.css 3.83 kB
release/woocommerce-payments/dist/multi-currency.js 25.5 kB
release/woocommerce-payments/dist/order-rtl.css 740 B
release/woocommerce-payments/dist/order.css 740 B
release/woocommerce-payments/dist/order.js 21.7 kB
release/woocommerce-payments/dist/plugins-page-rtl.css 484 B
release/woocommerce-payments/dist/plugins-page.css 484 B
release/woocommerce-payments/dist/plugins-page.js 2.64 kB
release/woocommerce-payments/dist/product-details-rtl.css 433 B
release/woocommerce-payments/dist/product-details.css 436 B
release/woocommerce-payments/dist/product-details.js 12.9 kB
release/woocommerce-payments/dist/settings-rtl.css 13.1 kB
release/woocommerce-payments/dist/settings.css 13 kB
release/woocommerce-payments/dist/settings.js 154 kB
release/woocommerce-payments/dist/subscription-edit-page.js 2 kB
release/woocommerce-payments/dist/subscription-product-onboarding-modal-rtl.css 527 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.css 527 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.js 1.95 kB
release/woocommerce-payments/dist/subscription-product-onboarding-toast.js 730 B
release/woocommerce-payments/dist/subscriptions-empty-state-rtl.css 120 B
release/woocommerce-payments/dist/subscriptions-empty-state.css 120 B
release/woocommerce-payments/dist/subscriptions-empty-state.js 1.87 kB
release/woocommerce-payments/dist/success.js 6.03 kB
release/woocommerce-payments/dist/tos-rtl.css 235 B
release/woocommerce-payments/dist/tos.css 235 B
release/woocommerce-payments/dist/tos.js 3.15 kB
release/woocommerce-payments/dist/wc-payments-review-prompt-rtl.css 1.68 kB
release/woocommerce-payments/dist/wc-payments-review-prompt.css 1.68 kB
release/woocommerce-payments/dist/wc-payments-review-prompt.js 14.5 kB
release/woocommerce-payments/dist/wc-payments-settings-spotlight-rtl.css 1.68 kB
release/woocommerce-payments/dist/wc-payments-settings-spotlight.css 1.68 kB
release/woocommerce-payments/dist/wc-payments-settings-spotlight.js 27.9 kB
release/woocommerce-payments/dist/woopay-direct-checkout.js 5.58 kB
release/woocommerce-payments/dist/woopay-express-button.js 22.1 kB
release/woocommerce-payments/dist/woopay-rtl.css 4.27 kB
release/woocommerce-payments/dist/woopay.css 4.25 kB
release/woocommerce-payments/dist/woopay.js 70.7 kB
release/woocommerce-payments/includes/subscriptions/assets/css/plugin-page.css 625 B
release/woocommerce-payments/includes/subscriptions/assets/js/plugin-page.js 814 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/i18n-loader.js 2.46 kB
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/jetpack-script-data.js 880 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/i18n-loader.js 1.02 kB
release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/script-data.js 69 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/babel.config.js 163 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.css 2.46 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.js 14.3 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.rtl.css 2.46 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.css 10.1 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.js 31.5 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.rtl.css 10.1 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.css 198 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.js 280 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.rtl.css 198 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.css 625 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.js 331 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.rtl.css 626 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-users.js 415 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-users-connection.js 159 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-ajax.js 520 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-callables.js 585 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-admin-create-user.css 218 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-admin-create-user.js 521 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.css 719 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.js 412 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-users.js 625 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/about.css 1.04 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-empty-state.css 294 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-order-statuses.css 408 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin.css 3.59 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/checkout.css 301 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/modal.css 746 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/view-subscription.css 574 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/wcs-upgrade.css 414 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin-pointers.js 543 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js 9.4 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.js 6.78 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.min.js 3.84 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-coupon.js 545 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-subscription.js 2.52 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.js 22.2 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.min.js 11.7 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/payment-method-restrictions.js 1.29 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/wcs-meta-boxes-order.js 507 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/payment-methods.js 358 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/single-product.js 428 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/view-subscription.js 1.38 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/wcs-cart.js 782 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/modal.js 1.09 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js 1.26 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.css 391 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.js 3.04 kB

compressed-size-action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multiple wcpay_track_new order actions can be scheduled on a single order

1 participant