PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.19
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.19
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
woocommerce-pos / includes / Sync / Request_Write_Queue.php

Request_Write_Queue.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.19, at includes/Sync/Request_Write_Queue.php

164 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Request-boundary write coalescing.
4 *
5 * @package WCPOS\WooCommercePOS\Sync
6 */
7
8 namespace WCPOS\WooCommercePOS\Sync;
9
10 /**
11 * Coalesce repeated saves into one write per record between flushes.
12 *
13 * WooCommerce saves records repeatedly within one request. Keep the last
14 * non-null payload per blog/type/id until the owner needs the settled write.
15 * A new record beyond capacity flushes first, bounding bulk-import queues;
16 * capacity one also preserves the journal's ordering across different orders.
17 * Entries retain their blog so multisite switches cannot redirect writes.
18 *
19 * Owners flush last on shutdown: WooCommerce saves the customer at priority
20 * 10 and the session at 20 (which can fire woocommerce_update_customer).
21 * The shutdown latch makes any later save write immediately, not wait for a
22 * flush that will never come. Draining before writes prevents replay after an
23 * exception and lets reentrant saves start a fresh queue.
24 */
25 final class Request_Write_Queue {
26 /**
27 * Maximum pending distinct records.
28 *
29 * @var int
30 */
31 private int $capacity;
32
33 /**
34 * Writer receiving type, id and payload under the entry's blog.
35 *
36 * @var callable
37 */
38 private $write;
39
40 /**
41 * Pending [blog, type, id, payload] entries.
42 *
43 * @var array<string, array>
44 */
45 private array $entries = array();
46
47 /**
48 * Whether the final request-boundary flush has begun.
49 *
50 * @var bool
51 */
52 private bool $shutdown_flushed = false;
53
54 /**
55 * Bind a writer and its pending-record capacity.
56 *
57 * @param int $capacity Maximum pending distinct records.
58 * @param callable $write Writer receiving type, id and payload.
59 */
60 public function __construct( int $capacity, callable $write ) {
61 $this->capacity = $capacity;
62 $this->write = $write;
63 }
64
65 /**
66 * Owe one write, or write immediately after shutdown.
67 *
68 * @param string $type Record type.
69 * @param int $id Record id.
70 * @param mixed $payload Optional write context; null retains existing context.
71 */
72 public function owe( string $type, int $id, $payload = null ): void {
73 if ( $this->shutdown_flushed ) {
74 ( $this->write )( $type, $id, $payload );
75 return;
76 }
77 $key = $this->key( $type, $id );
78 // A capacity flush runs writers that may re-enter owe(), so the pending
79 // state is re-read after each flush: the key may now be pending (keep it,
80 // merge the payload) or the queue full again. One re-check is enough for
81 // any real writer; a pathological one that refills the queue on every
82 // flush is then allowed a bounded overflow rather than an endless loop.
83 for ( $attempt = 0; $attempt < 2; $attempt++ ) {
84 if ( isset( $this->entries[ $key ] ) ) {
85 // After a flush the pending payload came from a re-entered writer and
86 // is newer than this call's; only fill a gap then.
87 if ( null !== $payload && ( 0 === $attempt || null === $this->entries[ $key ][3] ) ) {
88 $this->entries[ $key ][3] = $payload;
89 }
90 return;
91 }
92 if ( count( $this->entries ) < $this->capacity ) {
93 break;
94 }
95 $this->flush();
96 }
97 $this->entries[ $key ] = array( get_current_blog_id(), $type, $id, $payload );
98 }
99
100 /**
101 * Whether this blog owes a write for the record.
102 *
103 * @param string $type Record type.
104 * @param int $id Record id.
105 * @return bool
106 */
107 public function owes( string $type, int $id ): bool {
108 return isset( $this->entries[ $this->key( $type, $id ) ] );
109 }
110
111 /**
112 * Forget this blog's pending write without writing it.
113 *
114 * @param string $type Record type.
115 * @param int $id Record id.
116 */
117 public function drop( string $type, int $id ): void {
118 unset( $this->entries[ $this->key( $type, $id ) ] );
119 }
120
121 /** Drain before writing so exceptions and reentrant saves cannot replay entries. */
122 public function flush(): void {
123 $entries = $this->entries;
124 $this->entries = array();
125 foreach ( $entries as $entry ) {
126 list( $blog, $type, $id, $payload ) = $entry;
127 $switch = is_multisite() && get_current_blog_id() !== $blog;
128 if ( $switch ) {
129 switch_to_blog( $blog );
130 }
131 try {
132 ( $this->write )( $type, $id, $payload );
133 } finally {
134 if ( $switch ) {
135 restore_current_blog();
136 }
137 }
138 }
139 }
140
141 /** Latch even an empty queue so subsequent saves write immediately. */
142 public function flush_at_shutdown(): void {
143 $this->shutdown_flushed = true;
144 $this->flush();
145 }
146
147 /** Discard entries and the shutdown latch. Tests only. */
148 public function reset(): void {
149 $this->entries = array();
150 $this->shutdown_flushed = false;
151 }
152
153 /**
154 * Scope a record to its current blog.
155 *
156 * @param string $type Record type.
157 * @param int $id Record id.
158 * @return string
159 */
160 private function key( string $type, int $id ): string {
161 return get_current_blog_id() . ':' . $type . ':' . $id;
162 }
163 }
164