PluginProbe
WANotifier for Forms and Actions / 3.1.1
WANotifier for Forms and Actions v3.1.1
3.1.1 3.1.0 3.0.4 2.7.10 2.7.11 2.7.12 2.7.13 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 3.0.0 3.0.1 3.0.2 3.0.3 trunk 0.1.0 0.1.1 1.0.0 1.0.1 1.0.2 All 67 releases
notifier / libraries / action-scheduler / classes / ActionScheduler_OptionLock.php

ActionScheduler_OptionLock.php in WANotifier for Forms and Actions 3.1.1, at libraries/action-scheduler/classes/ActionScheduler_OptionLock.php

184 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Provide a way to set simple transient locks to block behaviour
5 * for up-to a given duration.
6 *
7 * Class ActionScheduler_OptionLock
8 *
9 * @since 3.0.0
10 */
11 class ActionScheduler_OptionLock extends ActionScheduler_Lock {
12
13 /**
14 * Set a lock using options for a given amount of time (60 seconds by default).
15 *
16 * Using an autoloaded option avoids running database queries or other resource intensive tasks
17 * on frequently triggered hooks, like 'init' or 'shutdown'.
18 *
19 * For example, ActionScheduler_QueueRunner->maybe_dispatch_async_request() uses a lock to avoid
20 * calling ActionScheduler_QueueRunner->has_maximum_concurrent_batches() every time the 'shutdown',
21 * hook is triggered, because that method calls ActionScheduler_QueueRunner->store->get_claim_count()
22 * to find the current number of claims in the database.
23 *
24 * @param string $lock_type A string to identify different lock types.
25 * @bool True if lock value has changed, false if not or if set failed.
26 */
27 public function set( $lock_type ) {
28 global $wpdb;
29
30 $now = time();
31 $lock_key = $this->get_key( $lock_type );
32 $existing_lock_value = $this->get_existing_lock( $lock_type, $now );
33 $new_lock_value = $this->new_lock_value( $lock_type, $now );
34
35 // The lock may not exist yet, or may have been deleted.
36 if ( null === $existing_lock_value ) {
37 $inserted = (bool) $wpdb->insert(
38 $wpdb->options,
39 array(
40 'option_name' => $lock_key,
41 'option_value' => $new_lock_value,
42 'autoload' => 'no',
43 )
44 );
45
46 // Sync cache as necessary.
47 if ( $inserted ) {
48 $ttl = $this->get_expiration_from( $new_lock_value ) - $now;
49 if ( $ttl > 0 ) {
50 wp_cache_set( $lock_key, $new_lock_value, 'action_scheduler_locks', $ttl );
51 }
52 }
53
54 return $inserted;
55 }
56
57 if ( $this->get_expiration_from( $existing_lock_value ) >= $now ) {
58 return false;
59 }
60
61 // Otherwise, try to obtain the lock.
62 $updated = (bool) $wpdb->update(
63 $wpdb->options,
64 array( 'option_value' => $new_lock_value ),
65 array(
66 'option_name' => $lock_key,
67 'option_value' => $existing_lock_value,
68 )
69 );
70
71 // Sync cache as necessary.
72 if ( $updated ) {
73 $ttl = $this->get_expiration_from( $new_lock_value ) - $now;
74 if ( $ttl > 0 ) {
75 wp_cache_set( $lock_key, $new_lock_value, 'action_scheduler_locks', $ttl );
76 }
77 } else {
78 // Compare-and-swap failed — another process acquired the lock between our read and write.
79 // Invalidate cache: the expired value may still be live in WP object cache (TTL=1 edge case) before the winner's wp_cache_set executes.
80 wp_cache_delete( $lock_key, 'action_scheduler_locks' );
81 }
82
83 return $updated;
84 }
85
86 /**
87 * If a lock is set, return the timestamp it was set to expiry.
88 *
89 * @param string $lock_type A string to identify different lock types.
90 * @return bool|int False if no lock is set, otherwise the timestamp for when the lock is set to expire.
91 */
92 public function get_expiration( $lock_type ) {
93 return $this->get_expiration_from( (string) $this->get_existing_lock( $lock_type, time() ) );
94 }
95
96 /**
97 * Given the lock string, derives the lock expiration timestamp (or false if it cannot be determined).
98 *
99 * @param string $lock_value String containing a timestamp, or pipe-separated combination of unique value and timestamp.
100 *
101 * @return false|int
102 */
103 private function get_expiration_from( $lock_value ) {
104 $lock_string = explode( '|', $lock_value );
105 $count = count( $lock_string );
106
107 // Old style lock?
108 if ( 1 === $count && is_numeric( $lock_string[0] ) ) {
109 return (int) $lock_string[0];
110 }
111
112 // New style lock?
113 if ( 2 === $count && is_numeric( $lock_string[1] ) ) {
114 return (int) $lock_string[1];
115 }
116
117 return false;
118 }
119
120 /**
121 * Get the key to use for storing the lock in the transient
122 *
123 * @param string $lock_type A string to identify different lock types.
124 * @return string
125 */
126 protected function get_key( $lock_type ) {
127 return sprintf( 'action_scheduler_lock_%s', $lock_type );
128 }
129
130 /**
131 * Supplies the existing lock value, or null if not set.
132 *
133 * @param string $lock_type A string to identify different lock types.
134 * @param int $now The timestamp to use.
135 *
136 * @return string|null
137 */
138 private function get_existing_lock( $lock_type, int $now ) {
139 global $wpdb;
140
141 $lock_key = $this->get_key( $lock_type );
142 $cached = wp_cache_get( $lock_key, 'action_scheduler_locks' );
143 if ( false !== $cached ) {
144 return (string) $cached;
145 }
146
147 $value = null;
148 // Now grab the existing lock value, if there is one.
149 // get_var() returns null for the empty string ('') so we must use get_row().
150 $row = $wpdb->get_row(
151 $wpdb->prepare(
152 "SELECT option_value FROM $wpdb->options WHERE option_name = %s",
153 $lock_key
154 )
155 );
156
157 if ( $row ) {
158 $value = $row->option_value;
159 // Sync cache as necessary.
160 $ttl = $this->get_expiration_from( $value ) - $now;
161 if ( $ttl > 0 ) {
162 wp_cache_set( $lock_key, $value, 'action_scheduler_locks', $ttl );
163 }
164 }
165
166 return $value;
167 }
168
169 /**
170 * Supplies a lock value consisting of a unique value and the current timestamp, which are separated by a pipe
171 * character.
172 *
173 * Example: (string) "649de012e6b262.09774912|1688068114"
174 *
175 * @param string $lock_type A string to identify different lock types.
176 * @param int $now The timestamp to use.
177 *
178 * @return string
179 */
180 private function new_lock_value( $lock_type, int $now ): string {
181 return uniqid( '', true ) . '|' . ( $now + $this->get_duration( $lock_type ) );
182 }
183 }
184