PluginProbe
Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution / 1.4.0
Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution v1.4.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 / Preload / Provider.php

Provider.php in Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution 1.4.0, at src/Performance/Preload/Provider.php

301 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The Service Provider for Preloading functionality.
4 *
5 * @package SolidWP\Performance
6 */
7
8 declare( strict_types=1 );
9
10 namespace SolidWP\Performance\Preload;
11
12 use SolidWP\Performance\Contracts\Service_Provider;
13 use SolidWP\Performance\Core;
14 use SolidWP\Performance\Lock\Contracts\Blockable_Lock;
15 use SolidWP\Performance\Lock\Lock_Factory;
16 use SolidWP\Performance\Log\Log;
17 use SolidWP\Performance\Preload\Contracts\Preloadable;
18 use SolidWP\Performance\Preload\Limiter\Batch_Limiter;
19 use SolidWP\Performance\Preload\Limiter\Core_Counter;
20 use SolidWP\Performance\Preload\Limiter\Sleep_Limiter;
21 use SolidWP\Performance\Preload\Monitor\Monitor;
22 use SolidWP\Performance\Preload\Sitemap\Repositories\Contracts\Sitemap_Repository;
23 use SolidWP\Performance\Preload\Sitemap\Repositories\Sitemap_Option_Repository;
24 use SolidWP\Performance\Preload\Sitemap\Sitemap;
25 use SolidWP\Performance\Preload\State\Enums\Source;
26 use SolidWP\Performance\Preload\State\State;
27 use SolidWP\Performance\Psr\Log\LoggerInterface;
28 use SolidWP\Performance\Symfony\Component\HttpClient\HttpClient;
29 use SolidWP\Performance\Symfony\Component\HttpClient\ScopingHttpClient;
30 use SolidWP\Performance\Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
31 use SolidWP\Performance\Symfony\Contracts\HttpClient\Exception\TimeoutExceptionInterface;
32 use SolidWP\Performance\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
33 use SolidWP\Performance\Symfony\Contracts\HttpClient\HttpClientInterface;
34 use Throwable;
35
36 /**
37 * The Service Provider for Preloading functionality.
38 *
39 * @package SolidWP\Performance
40 */
41 final class Provider extends Service_Provider {
42
43 public const PRELOADER_LOCK = 'solid_performance.preload.preloader_lock';
44
45 /**
46 * @inheritDoc
47 */
48 public function register(): void {
49 $this->register_http_client();
50 $this->register_sitemap_repository();
51 $this->register_preload_monitor();
52 $this->register_rate_limiter();
53 $this->register_preloader();
54 $this->register_preload_scheduler();
55 }
56
57 /**
58 * Register the underlying Solid Performance Preloader HTTP Client.
59 *
60 * @return void
61 */
62 private function register_http_client(): void {
63 $this->container->singleton(
64 HttpClientInterface::class,
65 function () {
66 $options = [
67 'headers' => [
68 'User-Agent' => sprintf(
69 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 (compatible; Solid Performance/%s)',
70 $this->container->get( Core::PLUGIN_VERSION )
71 ),
72 ],
73 'verify_peer' => apply_filters( 'https_local_ssl_verify', false ),
74 'verify_host' => apply_filters( 'https_local_ssl_verify', false ),
75 ];
76
77 $proxy_url = $this->container->get( Proxy::class )->url();
78
79 if ( $proxy_url ) {
80 $options['proxy'] = $proxy_url;
81 }
82
83 $logger = $this->container->get( LoggerInterface::class );
84
85 $client = ScopingHttpClient::forBaseUri( HttpClient::create(), home_url(), $options );
86
87 $redirect_client = new Redirect_Client( $client );
88 $redirect_client->setLogger( $logger );
89
90 return $redirect_client;
91 }
92 );
93 }
94
95 /**
96 * Register the concrete repository to use for the Sitemap Repository.
97 *
98 * @return void
99 */
100 private function register_sitemap_repository(): void {
101 $this->container->when( Sitemap::class )
102 ->needs( '$url' )
103 ->give(
104 function (): string {
105 // We assume this automatically redirects to the correct sitemap.
106 return home_url( '/sitemap.xml' );
107 }
108 );
109
110 $this->container->bind( Sitemap_Repository::class, Sitemap_Option_Repository::class );
111 }
112
113 /**
114 * Register the preload monitor to check for a stalled preloader.
115 *
116 * @return void
117 */
118 private function register_preload_monitor(): void {
119 /**
120 * Filter how many seconds to wait before we check for a stalled preloader.
121 *
122 * @param int $timeout The timeout in seconds.
123 */
124 $timeout = (int) apply_filters( 'solidwp/performance/preload/monitor/timeout', 12 );
125
126 /**
127 * Filter the maximum number of times we retry a stalled preloader before giving up.
128 *
129 * @param int $max_retries The number of retries.
130 */
131 $max_retries = (int) apply_filters( 'solidwp/performance/preload/monitor/max_retries', 10 );
132
133 $this->container->when( Monitor::class )
134 ->needs( '$timeout' )
135 ->give( static fn(): int => $timeout );
136
137 $this->container->when( Monitor::class )
138 ->needs( '$max_retries' )
139 ->give( static fn(): int => $max_retries );
140 }
141
142 /**
143 * Register the rate limiter that controls the batch size of the preloader.
144 *
145 * @return void
146 */
147 private function register_rate_limiter(): void {
148 $this->container->singleton( Core_Counter::class, Core_Counter::class );
149 $this->container->singleton( Sleep_Limiter::class, Sleep_Limiter::class );
150 $this->container->singleton( Batch_Limiter::class, Batch_Limiter::class );
151
152 /**
153 * Filters the maximum load per CPU core before we start rate limiting requests.
154 *
155 * 0-0.7 per core is optimal range, allowing headroom.
156 * 0.7-1.0 per core is high, but manageable.
157 * 1.0+ per core indicates CPU is likely bottle necked.
158 *
159 * @param float $max_load_per_core The maximum load per core before we begin rate limiting preloading.
160 */
161 $max_load_per_core = apply_filters( 'solidwp/performance/preload/max_load_per_core', 0.5 );
162
163 /**
164 * Filters the delay multiplier to calculate the sleep time.
165 *
166 * @param float $delay The delay multiplier to determine how much more sleep based on system load.
167 */
168 $delay = apply_filters( 'solidwp/performance/preload/sleep_multiplier', 1.5 );
169
170 $this->container->when( Sleep_Limiter::class )
171 ->needs( '$max_load_per_core' )
172 ->give( static fn(): float => $max_load_per_core );
173
174 $this->container->when( Sleep_Limiter::class )
175 ->needs( '$delay' )
176 ->give( static fn(): float => $delay );
177
178 /**
179 * Filter how many URLs are processed per batch.
180 *
181 * @param int $batch_size The number of URLs to process per batch.
182 */
183 $batch_size = apply_filters( 'solidwp/performance/preload/batch_size', 50 );
184
185 /**
186 * Filter the decay value for how much we'll reduce the batch size when server load is high.
187 *
188 * @param float $decay The negative value used to calculate exponential decay.
189 */
190 $decay = apply_filters( 'solidwp/performance/preload/decay', -0.9 );
191
192 $this->container->when( Batch_Limiter::class )
193 ->needs( '$batch_size' )
194 ->give( static fn(): int => $batch_size );
195
196 $this->container->when( Batch_Limiter::class )
197 ->needs( '$max_load_per_core' )
198 ->give( static fn(): float => $max_load_per_core );
199
200 $this->container->when( Batch_Limiter::class )
201 ->needs( '$decay' )
202 ->give( static fn(): float => $decay );
203 }
204
205 /**
206 * Register the Preloader.
207 *
208 * @return void
209 */
210 private function register_preloader(): void {
211 /**
212 * Filters how long to sleep between batches.
213 *
214 * @param int $seconds How to sleep for in seconds.
215 */
216 $seconds = apply_filters( 'solidwp/performance/preload/batch/sleep/seconds', 1 );
217
218 $this->container->when( Preloader::class )
219 ->needs( '$sleep' )
220 ->give( static fn(): int => $seconds );
221
222 $this->container->singleton( Preloadable::class, Preloader::class );
223
224 add_action( 'template_redirect', $this->container->callback( Preloadable::class, 'preload' ), 0 );
225
226 // Automatically try to preload URLs after they are purged.
227 add_action(
228 'solidwp/performance/cache/purge/url/after',
229 function ( string $url ) {
230 $preloader = $this->container->get( Preloadable::class );
231
232 try {
233 $preloader->preload_url( $url );
234 } catch ( TimeoutExceptionInterface |TransportExceptionInterface $e ) {
235 do_action(
236 Log::INFO,
237 'Timeout for preload {url}',
238 [
239 'url' => $url,
240 'exception' => $e,
241 ]
242 );
243 } catch ( ClientExceptionInterface $e ) {
244 do_action(
245 Log::WARNING,
246 'Failed to preload {url}',
247 [
248 'url' => $url,
249 'exception' => $e,
250 ]
251 );
252 } catch ( Throwable $e ) {
253 do_action(
254 Log::ERROR,
255 'Failed to preload {url}',
256 [
257 'url' => $url,
258 'exception' => $e,
259 ]
260 );
261 }
262 },
263 20,
264 1
265 );
266 }
267
268 /**
269 * Register the preload scheduler.
270 *
271 * @return void
272 */
273 private function register_preload_scheduler(): void {
274 $this->container->singleton( State::class, State::class );
275
276 /**
277 * Filter how long the preloader stays locked until it's canceled or completes.
278 *
279 * @param int $ttl How long in seconds the preloader will be locked until it's canceled or completes.
280 */
281 $ttl = apply_filters( 'solidwp/performance/preload/lock/ttl', DAY_IN_SECONDS );
282
283 $this->container->singleton(
284 self::PRELOADER_LOCK,
285 function () use ( $ttl ): Blockable_Lock {
286 $owner = Source::detect_source();
287
288 return $this->container->get( Lock_Factory::class )->make( 'is_preloading', $ttl, $owner );
289 }
290 );
291
292 $this->container->singleton( Preload_Scheduler::class, Preload_Scheduler::class );
293
294 $this->container->when( Preload_Scheduler::class )
295 ->needs( Blockable_Lock::class )
296 ->give(
297 fn(): Blockable_Lock => $this->container->get( self::PRELOADER_LOCK )
298 );
299 }
300 }
301