| @@ -36,8 +36,22 @@ | ||
| 36 | 36 | * |
| 37 | 37 | * @var bool |
| 38 | 38 | */ |
| 39 | 39 | private static $host_pressure_checked = false; |
| 40 | + | |
| 41 | + /** | |
| 42 | + * Host CPU count, or null when unavailable. | |
| 43 | + * | |
| 44 | + * @var int|null | |
| 45 | + */ | |
| 46 | + private static $host_cpu_count = null; | |
| 47 | + | |
| 48 | + /** | |
| 49 | + * Whether the host CPU count has been resolved. | |
| 50 | + * | |
| 51 | + * @var bool | |
| 52 | + */ | |
| 53 | + private static $host_cpu_count_resolved = false; | |
| 40 | 54 | // phpcs:disable Squiz.Commenting.FunctionComment.MissingParamTag, Squiz.Commenting.FunctionComment.Missing -- Typed signatures keep this bootstrap path within its strict size budget. |
| 41 | 55 | /** Detect an exact raw ping request. */ |
| 42 | 56 | public static function matches_request( string $method, string $request_uri, ?string $rest_route ): bool { |
| 43 | 57 | if ( 'GET' !== $method && 'HEAD' !== $method ) { |
| @@ -47,8 +61,34 @@ | ||
| 47 | 61 | |
| 48 | 62 | return self::ROUTE === $rest_route || ( \strlen( $path ) >= \strlen( self::PRETTY_ROUTE ) && self::PRETTY_ROUTE === substr( $path, -\strlen( self::PRETTY_ROUTE ) ) ); |
| 49 | 63 | } |
| 50 | 64 | |
| 65 | + /** | |
| 66 | + * Response headers that keep the ping out of proxy and server caches. | |
| 67 | + * | |
| 68 | + * The fast path answers before WP REST exists, so Rest_Cors never adds | |
| 69 | + * its cache-defeating headers here; without these an origin page cache | |
| 70 | + * served one host's ping (timestamp and pressure bucket) frozen for its | |
| 71 | + * whole TTL (measured 2026-09-16). Same Cache-Control value as Rest_Cors. | |
| 72 | + * | |
| 73 | + * @return array<string, string> | |
| 74 | + */ | |
| 75 | + public static function cache_defeating_headers(): array { | |
| 76 | + return array( | |
| 77 | + 'Cache-Control' => 'private, no-store', | |
| 78 | + 'X-LiteSpeed-Cache-Control' => 'no-cache', | |
| 79 | + ); | |
| 80 | + } | |
| 81 | + | |
| 82 | + /** Belt and braces for drop-in page caches that finalise at shutdown and read constants, not headers. */ | |
| 83 | + private static function forbid_page_cache(): void { | |
| 84 | + foreach ( array( 'DONOTCACHEPAGE', 'LSCACHE_NO_CACHE' ) as $constant ) { | |
| 85 | + if ( ! \defined( $constant ) ) { | |
| 86 | + \define( $constant, true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- third-party constant. | |
| 87 | + } | |
| 88 | + } | |
| 89 | + } | |
| 90 | + | |
| 51 | 91 | /** Serve a matching request before the remaining plugins load. */ |
| 52 | 92 | public static function maybe_serve(): void { |
| 53 | 93 | $method = isset( $_SERVER['REQUEST_METHOD'] ) && \is_string( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : ''; |
| 54 | 94 | if ( 'GET' !== $method && 'HEAD' !== $method ) { |
| @@ -62,10 +102,14 @@ | ||
| 62 | 102 | if ( ! self::matches_request( $method, $request_uri, $rest_route ) ) { |
| 63 | 103 | return; |
| 64 | 104 | } |
| 65 | 105 | $data = self::payload(); |
| 106 | + self::forbid_page_cache(); | |
| 66 | 107 | http_response_code( 200 ); |
| 67 | 108 | header( 'Content-Type: application/json; charset=UTF-8' ); |
| 109 | + foreach ( self::cache_defeating_headers() as $name => $value ) { | |
| 110 | + header( $name . ': ' . $value ); | |
| 111 | + } | |
| 68 | 112 | header( 'Access-Control-Allow-Origin: *' ); |
| 69 | 113 | // Deliberately just the one header this fast path can emit, not the |
| 70 | 114 | // full Rest_Cors::EXPOSE_HEADERS set: this short-circuits before the |
| 71 | 115 | // autoloader and WP REST exist. The OPTIONS preflight for this route |
| @@ -136,22 +180,33 @@ | ||
| 136 | 180 | |
| 137 | 181 | return $load <= 1.8 ? 'elevated' : 'high'; |
| 138 | 182 | } |
| 139 | 183 | |
| 140 | - /** Read the host load average and convert it to a bucket, or null when unavailable. */ | |
| 184 | + /** | |
| 185 | + * Use only /proc/cpuinfo because sys_getloadavg() reads host-wide /proc/loadavg, | |
| 186 | + * so its CPU divisor must share the host namespace rather than a container quota. | |
| 187 | + */ | |
| 188 | + public static function cpu_count_from_cpuinfo( ?string $cpuinfo ): ?int { | |
| 189 | + $found = null !== $cpuinfo ? preg_match_all( '/^processor\s*:/m', $cpuinfo ) : false; | |
| 190 | + | |
| 191 | + return \is_int( $found ) && $found > 0 ? $found : null; | |
| 192 | + } | |
| 193 | + | |
| 194 | + /** | |
| 195 | + * Normalize host load using the /proc/cpuinfo CPU count. | |
| 196 | + * Unknown counts yield null (no header), rather than misleading pressure from a guessed divisor. | |
| 197 | + */ | |
| 141 | 198 | private static function read_host_pressure_bucket(): ?string { |
| 142 | 199 | if ( ! \function_exists( 'sys_getloadavg' ) || ! \is_array( $average = @sys_getloadavg() ) || ! isset( $average[0] ) ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure -- call only after availability check. |
| 143 | 200 | return null; |
| 144 | 201 | } |
| 145 | - /** @var int|null $cpus */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- local static type. | |
| 146 | - static $cpus = null; | |
| 147 | - if ( null === $cpus ) { | |
| 148 | - $cpuinfo = @file_get_contents( '/proc/cpuinfo' ); | |
| 149 | - $found = \is_string( $cpuinfo ) ? preg_match_all( '/^processor\s*:/m', $cpuinfo ) : false; | |
| 150 | - $cpus = \is_int( $found ) && $found > 0 ? $found : 1; | |
| 202 | + if ( ! self::$host_cpu_count_resolved ) { | |
| 203 | + $cpuinfo = @file_get_contents( '/proc/cpuinfo' ); | |
| 204 | + self::$host_cpu_count = self::cpu_count_from_cpuinfo( false === $cpuinfo ? null : $cpuinfo ); | |
| 205 | + self::$host_cpu_count_resolved = true; | |
| 151 | 206 | } |
| 152 | 207 | |
| 153 | - return self::pressure_bucket( (float) $average[0] / $cpus ); | |
| 208 | + return null === self::$host_cpu_count ? null : self::pressure_bucket( (float) $average[0] / self::$host_cpu_count ); | |
| 154 | 209 | } |
| 155 | 210 | |
| 156 | 211 | /** @return array<string, bool|int|string> */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- compact typed payload. |
| 157 | 212 | private static function payload(): array { |