Commit 275d706dc8f for woocommerce
commit 275d706dc8fb6e3156d2000e01a36ee7e9cd0a62
Author: AnnaMag <AnnaMag@users.noreply.github.com>
Date: Thu Oct 22 13:28:18 2026 +0200
Send ability events without finishing the response early (#70119)
* Send ability events without finishing the response early
The early finish saved about 35 ms per MCP ability call on a PHP-FPM store,
which the client could not see, and changed the request lifecycle for every
plugin on those requests. Events now go out at shutdown like every other
WooCommerce Tracks event. Removes the woocommerce_ability_tracking_finish_response
filter, which has not shipped in a release.
* Check that a second shutdown does not send the events again
* Describe the ability pixel send as happening now
diff --git a/plugins/woocommerce/src/Internal/Abilities/AbilitiesTracking.php b/plugins/woocommerce/src/Internal/Abilities/AbilitiesTracking.php
index bf8155ab9d5..06923ef2a89 100644
--- a/plugins/woocommerce/src/Internal/Abilities/AbilitiesTracking.php
+++ b/plugins/woocommerce/src/Internal/Abilities/AbilitiesTracking.php
@@ -189,7 +189,7 @@ class AbilitiesTracking {
add_filter( 'mcp_adapter_pre_tool_call', array( $this, 'handle_mcp_adapter_pre_tool_call' ) );
add_filter( 'rest_request_before_callbacks', array( $this, 'handle_rest_request_before_callbacks' ), 10, 3 );
add_filter( 'rest_request_after_callbacks', array( $this, 'handle_rest_request_after_callbacks' ), 10, 3 );
- // Last, so every other shutdown handler still finishes before the response does.
+ // Last, so ability calls made by other shutdown handlers are recorded too.
add_action( 'shutdown', array( $this, 'handle_shutdown' ), PHP_INT_MAX );
}
@@ -443,22 +443,6 @@ class AbilitiesTracking {
return;
}
- try {
- /**
- * Filters whether the response is sent to the client before the ability events go to Tracks, on servers that
- * support it (PHP-FPM, LiteSpeed). Return false to keep the client waiting for the send.
- *
- * @since 11.3.0
- *
- * @param bool $finish_response Whether to finish the response first. Default true.
- */
- if ( ! $this->is_long_running_process() && apply_filters( 'woocommerce_ability_tracking_finish_response', true ) ) {
- $this->finish_response();
- }
- } catch ( \Throwable $e ) {
- unset( $e );
- }
-
$this->flush_events();
// This runs after WC_Tracks_Client's own send at priority 20, so the queued pixels are sent here.
try {
@@ -483,23 +467,7 @@ class AbilitiesTracking {
}
/**
- * Send the response to the client and keep running, where the server supports it (PHP-FPM, LiteSpeed), as
- * wp-cron.php and WC_Background_Emailer do. Elsewhere the client keeps waiting for the send, as before.
- */
- protected function finish_response(): void {
- // Only one request can hold a PHP session; release it so the client's next request doesn't wait.
- if ( session_id() ) {
- session_write_close();
- }
- if ( function_exists( 'fastcgi_finish_request' ) ) {
- fastcgi_finish_request();
- } elseif ( function_exists( 'litespeed_finish_request' ) ) {
- litespeed_finish_request();
- }
- }
-
- /**
- * Send the pixels queued in the batched Tracks client now, instead of on shutdown.
+ * Send the pixels queued in the batched Tracks client now.
*/
protected function send_queued_pixels(): void {
\WC_Tracks_Client::send_batched_pixels();
diff --git a/plugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesTrackingTest.php b/plugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesTrackingTest.php
index bf6ac285d61..08e9ddaaf7d 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesTrackingTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesTrackingTest.php
@@ -699,33 +699,21 @@ class AbilitiesTrackingTest extends WC_Unit_Test_Case {
}
/**
- * @testdox Should finish the response before sending events, unless there are none, it runs under WP-CLI, or a filter says not to.
+ * @testdox Should send the queued events on shutdown, and only when there are some.
*/
- public function test_finishes_the_response_before_sending_events(): void {
+ public function test_sends_queued_events_on_shutdown(): void {
$this->start_mcp_tool_call();
$this->sut->handle_shutdown();
- $this->assertSame( array(), $this->sut->finished_after, 'There was nothing to send' );
- $this->assertSame( array(), $this->sut->pixels_sent_after, 'Nothing to send' );
+ $this->assertSame( 0, $this->sut->pixel_sends, 'There was nothing to send' );
wp_get_ability( self::ABILITY_ID )->execute();
$this->sut->handle_shutdown();
- $this->assertSame( array( 0 ), $this->sut->finished_after, 'Once, before the event was sent' );
- $this->assertSame( array( 1 ), $this->sut->pixels_sent_after, 'The pixels go out after the response is finished' );
-
- add_filter( 'woocommerce_ability_tracking_finish_response', '__return_false' );
- try {
- wp_get_ability( self::ABILITY_ID )->execute();
- $this->sut->handle_shutdown();
- } finally {
- remove_filter( 'woocommerce_ability_tracking_finish_response', '__return_false' );
- }
- $this->assertSame( array( 0 ), $this->sut->finished_after, 'The filter keeps the response open' );
- $this->assertSame( array( 1, 1 ), $this->sut->pixels_sent_after, 'The events are still sent' );
+ $this->assertSame( 1, $this->sut->pixel_sends, 'Sent once' );
+ $this->assertCount( 1, $this->sut->recorded );
- $this->sut->long_running = true;
- wp_get_ability( self::ABILITY_ID )->execute();
$this->sut->handle_shutdown();
- $this->assertSame( array( 0 ), $this->sut->finished_after, 'No client waits under WP-CLI' );
+ $this->assertSame( 1, $this->sut->pixel_sends, 'A flushed queue is not sent again' );
+ $this->assertCount( 1, $this->sut->recorded );
}
/**
@@ -1119,32 +1107,18 @@ class AbilitiesTrackingTest extends WC_Unit_Test_Case {
}
/**
- * Never send real pixels from tests; note when they would be sent.
- */
- protected function send_queued_pixels(): void {
- $this->pixels_sent_after[] = count( $this->finished_after );
- }
-
- /**
- * How many events had been sent each time the response was finished.
+ * How many times the queued pixels would have been sent.
*
- * @var int[]
+ * @var int
*/
- public array $finished_after = array();
+ public int $pixel_sends = 0;
/**
- * Note when the response would be finished.
+ * Never send real pixels from tests; count the sends instead.
*/
- protected function finish_response(): void {
- $this->finished_after[] = count( $this->recorded );
+ protected function send_queued_pixels(): void {
+ ++$this->pixel_sends;
}
-
- /**
- * How many times the response had been finished each time the queued pixels were sent.
- *
- * @var int[]
- */
- public array $pixels_sent_after = array();
};
}