PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.18
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.18
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
← All changes | includes/Sync/Sync_Journal.php +25 -77 1.10.131.10.18 View file →
@@ -37,29 +37,16 @@
37 37 * rows and ~66 ms for ONE online order, measured 2026-09-03 on dev-next.
38 38 * A journal row is a change POINTER (ADR 0033), so one row per order per
39 39 * request carries the same information.
40 40 *
41 - * Single slot, not a map: a save for a DIFFERENT order flushes the pending
42 - * one first (so a bulk loop never holds rows until process end), which
43 - * means at most one order is ever pending. Static, not per instance: the
44 - * "update row lands before any other-origin row" guarantee must hold for
45 - * whichever `Sync_Journal` instance writes the other row. The slot keeps the
46 - * blog id so a multisite `switch_to_blog()` between save and flush still
47 - * writes to the originating site's table, and the order object the hook
48 - * handed us so the flush never refetches.
49 - *
50 - * Rows land on {@see flush_pending_order_updates()}: at `shutdown` (last,
51 - * after WooCommerce's own shutdown saves), before any other-origin row, or
52 - * when a different order is saved. Once the shutdown flush has run, later
53 - * updates write immediately.
54 - *
55 - * @var array{blog: int, id: int, order: \WC_Abstract_Order|null}|null
41 + * Capacity one preserves ordering before a different order or other-origin
42 + * row. Static so that guarantee holds across journal instances. The first
43 + * instance needing the queue binds its writer, including after shutdown;
44 + * all instances write the same table. Retaining the hook's order object
45 + * avoids a refetch. See Request_Write_Queue for the queue mechanics.
56 46 */
57 - private static ?array $pending_order_update = null;
47 + private static ?Request_Write_Queue $pending_updates = null;
58 48
59 - /** Set by the shutdown flush; afterwards updates are written immediately. */
60 - private static bool $shutdown_flushed = false;
61 -
62 49 /**
63 50 * Option-name prefix for the per-object-type lossy-prune watermarks.
64 51 *
65 52 * The watermark is scoped per object type for the same reason heads are
@@ -536,9 +523,9 @@
536 523
537 524 /**
538 525 * Mark an order's `hook:update` row as owed; the row lands on flush.
539 526 *
540 - * See {@see $pending_order_updates} for why this is deferred. Direct callers
527 + * See {@see $pending_updates} for why this is deferred. Direct callers
541 528 * that need an immediate row use {@see record_order_change()}.
542 529 *
543 530 * @param int $order_id Order id from the hook.
544 531 * @param \WC_Abstract_Order|mixed $order Order object from the hook (second
@@ -547,27 +534,9 @@
547 534 * refetch at flush time.
548 535 */
549 536 public function record_order_updated( int $order_id, $order = null ): void {
550 537 $order = $order instanceof \WC_Abstract_Order ? $order : null;
551 - if ( self::$shutdown_flushed ) {
552 - // The request boundary has passed (a save triggered by another
553 - // shutdown handler): nothing will flush again, so write now.
554 - $this->record_order_change( $order_id, 'hook:update', false, $order );
555 - return;
556 - }
557 - $blog = get_current_blog_id();
558 - $slot = self::$pending_order_update;
559 - if ( null !== $slot && ( $slot['id'] !== $order_id || $slot['blog'] !== $blog ) ) {
560 - // A different order began: land what is owed so a bulk loop (WP-CLI
561 - // import, Action Scheduler runner) never holds rows until process end.
562 - $this->flush_pending_order_updates();
563 - $slot = null;
564 - }
565 - self::$pending_order_update = array(
566 - 'blog' => $blog,
567 - 'id' => $order_id,
568 - 'order' => $order ?? ( $slot['order'] ?? null ),
569 - );
538 + $this->queue()->owe( 'order', $order_id, $order );
570 539 }
571 540
572 541 /**
573 542 * Write the owed `hook:update` row, if any.
@@ -576,19 +545,11 @@
576 545 * from the shutdown flush. Safe to call repeatedly: a flushed order is no
577 546 * longer pending.
578 547 */
579 548 public function flush_pending_order_updates(): void {
580 - $slot = self::$pending_order_update;
581 - if ( null === $slot ) {
582 - return;
549 + if ( null !== self::$pending_updates ) {
550 + self::$pending_updates->flush();
583 551 }
584 - self::$pending_order_update = null;
585 - self::in_blog(
586 - $slot['blog'],
587 - function () use ( $slot ): void {
588 - $this->record_order_change( $slot['id'], 'hook:update', false, $slot['order'] );
589 - }
590 - );
591 552 }
592 553
593 554 /**
594 555 * The `shutdown` callback: flush, then write every later update immediately.
@@ -593,45 +554,33 @@
593 554 /**
594 555 * The `shutdown` callback: flush, then write every later update immediately.
595 556 */
596 557 public function flush_pending_order_updates_at_shutdown(): void {
597 - self::$shutdown_flushed = true;
598 - $this->flush_pending_order_updates();
558 + $this->queue()->flush_at_shutdown();
599 559 }
600 560
601 561 /**
602 562 * Discard per-request coalescing state. Tests only: the PHPUnit process
603 - * never reaches `shutdown`, so the static slot and flag would leak between
563 + * never reaches `shutdown`, so the static queue would leak between
604 564 * test cases otherwise.
605 565 *
606 566 * @internal
607 567 */
608 568 public static function reset_request_state(): void {
609 - self::$pending_order_update = null;
610 - self::$shutdown_flushed = false;
569 + self::$pending_updates = null;
611 570 }
612 571
613 - /**
614 - * Run a write under the blog it was recorded on.
615 - *
616 - * The journal table is blog-scoped, so a deferred write must not follow a
617 - * `switch_to_blog()` that happened between the save and the flush.
618 - *
619 - * @param int $blog_id Blog the write belongs to.
620 - * @param callable $write The write.
621 - */
622 - private static function in_blog( int $blog_id, callable $write ): void {
623 - $switch = is_multisite() && get_current_blog_id() !== $blog_id;
624 - if ( $switch ) {
625 - switch_to_blog( $blog_id );
572 + /** Bind the first journal instance to the request's shared queue. */
573 + private function queue(): Request_Write_Queue {
574 + if ( null === self::$pending_updates ) {
575 + self::$pending_updates = new Request_Write_Queue(
576 + 1,
577 + function ( $type, $id, $order ): void {
578 + $this->record_order_change( $id, 'hook:update', false, $order );
579 + }
580 + );
626 581 }
627 - try {
628 - $write();
629 - } finally {
630 - if ( $switch ) {
631 - restore_current_blog();
632 - }
633 - }
582 + return self::$pending_updates;
634 583 }
635 584
636 585 public function record_order_deleted( int $order_id ): void {
637 586 $this->record_order_change( $order_id, 'hook:delete', true );
@@ -687,14 +636,13 @@
687 636 */
688 637 public function record_order_change( int $order_id, string $origin, bool $deleted, $order = null ): bool {
689 638 global $wpdb;
690 639 if ( 'hook:update' !== $origin ) {
691 - $slot = self::$pending_order_update;
692 - if ( 'hook:create' === $origin && null !== $slot && $order_id === $slot['id'] && get_current_blog_id() === $slot['blog'] ) {
640 + if ( 'hook:create' === $origin && $this->queue()->owes( 'order', $order_id ) ) {
693 641 // The Store API saves a checkout-draft several times BEFORE
694 642 // `woocommerce_new_order` fires. Both rows would point at the same
695 643 // live record, so the create row makes the owed update row redundant.
696 - self::$pending_order_update = null;
644 + $this->queue()->drop( 'order', $order_id );
697 645 } else {
698 646 // Land the owed update row FIRST so the stream never reads as
699 647 // delete-then-update (a replay would resurrect a trashed order).
700 648 $this->flush_pending_order_updates();