PluginProbe
Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution / 1.9.0
Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution v1.9.0
2.0.1 trunk 1.0.0 1.1.0 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 1.4.2 1.5.0 1.6.0 1.6.1 1.7.0 1.7.1 1.8.0 1.9.0 2.0.0
solid-performance / src / Performance / Lock / Drivers / Database_Driver.php

Database_Driver.php in Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution 1.9.0, at src/Performance/Lock/Drivers/Database_Driver.php

197 lines 4.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The database lock driver. Stores locks in a custom table.
4 *
5 * @package SolidWP\Performance
6 */
7
8 declare( strict_types=1 );
9
10 namespace SolidWP\Performance\Lock\Drivers;
11
12 use SolidWP\Performance\Lock\Contracts\Lock;
13 use SolidWP\Performance\Lock\Lock_Entry;
14 use SolidWP\Performance\StellarWP\DB\DB;
15 use SolidWP\Performance\StellarWP\DB\QueryBuilder\QueryBuilder;
16 use Throwable;
17
18 /**
19 * The database lock driver.
20 *
21 * @package SolidWP\Performance
22 */
23 final class Database_Driver implements Lock {
24
25 /**
26 * @var Lock_Entry
27 */
28 private Lock_Entry $lock_entry;
29
30 /**
31 * The un-prefixed table name for storing locks.
32 *
33 * @var string
34 */
35 private string $table;
36
37 /**
38 * The prune probability odds.
39 *
40 * @var array{0: int, 1: int}
41 */
42 private array $lottery;
43
44 /**
45 * @param Lock_Entry $lock_entry The lock entry.
46 * @param string $table The un-prefixed database table name where locks are stored.
47 * @param array{0: int, 1: int} $lottery The prune probability odds.
48 */
49 public function __construct(
50 Lock_Entry $lock_entry,
51 string $table,
52 array $lottery = [ 2, 100 ]
53 ) {
54 $this->lock_entry = $lock_entry;
55 $this->table = $table;
56 $this->lottery = $lottery;
57 }
58
59 /**
60 * Attempt to acquire a lock.
61 *
62 * @return bool
63 */
64 public function acquire(): bool {
65 global $wpdb;
66
67 // Don't log duplicate key errors to the error log.
68 $suppress = $wpdb->suppress_errors();
69
70 try {
71 $this->query()->insert(
72 [
73 'lock_name' => $this->lock_entry->name(),
74 'lock_owner' => $this->lock_entry->owner(),
75 'expiration' => $this->expires_at(),
76 ]
77 );
78
79 $acquired = true;
80 } catch ( Throwable $e ) {
81 $updated = $this->query()->where( 'lock_name', $this->lock_entry->name() )
82 ->where( 'expiration', time(), '<=' )
83 ->update(
84 [
85 'lock_owner' => $this->lock_entry->owner(),
86 'expiration' => $this->expires_at(),
87 ]
88 );
89
90 $acquired = $updated >= 1;
91 }
92
93 // Resume original error suppression.
94 $wpdb->suppress_errors( $suppress );
95
96 // Lottery to randomly prune expired locks. Move to a WP cron job one day.
97 if ( random_int( 1, $this->lottery[1] ) <= $this->lottery[0] ) {
98 $table = $wpdb->prefix . $this->table;
99
100 $query = DB::raw(
101 "
102 DELETE FROM $table
103 WHERE `expiration` <= %d
104 ",
105 [
106 time(),
107 ]
108 );
109
110 DB::query( $query->sql );
111 }
112
113 return $acquired;
114 }
115
116 /**
117 * Determine if this lock is already acquired.
118 *
119 * @return bool
120 */
121 public function is_acquired(): bool {
122 // Grab the CONSTANT 1, should be most efficient.
123 $sql = $this->query()->select( '1' )
124 ->where( 'lock_name', $this->lock_entry->name() )
125 ->where( 'expiration', time(), '>' )
126 ->limit( 1 )
127 ->getSQL();
128
129 return (bool) DB::get_var( $sql );
130 }
131
132 /**
133 * Get the original owner of the lock.
134 *
135 * @return string
136 */
137 public function owner(): string {
138 return (string) DB::get_var(
139 $this->query()->select( 'lock_owner' )
140 ->where( 'lock_name', $this->lock_entry->name() )
141 ->getSQL()
142 );
143 }
144
145 /**
146 * Release the lock, if the original owner matches the current one.
147 *
148 * @return bool
149 */
150 public function release(): bool {
151 $owner = $this->owner();
152
153 if ( ! strlen( $owner ) ) {
154 return false;
155 }
156
157 if ( $owner !== $this->lock_entry->owner() ) {
158 return false;
159 }
160
161 return $this->force_release();
162 }
163
164 /**
165 * Force release the lock, regardless of who owns it.
166 *
167 * @return bool
168 */
169 public function force_release(): bool {
170 return (bool) $this->query()
171 ->where( 'lock_name', $this->lock_entry->name() )
172 ->delete();
173 }
174
175 /**
176 * Determine the expiration time based on now + the lock's expiration in seconds.
177 *
178 * @return int
179 */
180 private function expires_at(): int {
181 $seconds = $this->lock_entry->expiration() > 0
182 ? $this->lock_entry->expiration()
183 : YEAR_IN_SECONDS * 10; // 10 years in seconds.
184
185 return time() + $seconds;
186 }
187
188 /**
189 * Get the query builder for this table.
190 *
191 * @return QueryBuilder
192 */
193 private function query(): QueryBuilder {
194 return DB::table( $this->table );
195 }
196 }
197