Commit f869547e533 for woocommerce

commit f869547e533f41e4ef216ad5c19fbcc4dd4cabf1
Author: Boro Sitnikovski <buritomath@gmail.com>
Date:   Thu Mar 26 12:27:51 2026 +0100

    [Performance] Add timeout to DataSourcePoller and skip redundant HTTP calls in wc_admin_daily (#63738)

    * [Performance] Add timeout to DataSourcePoller and skip redundant HTTP calls in wc_admin_daily

    - Add explicit 3s timeout to wp_remote_get() in DataSourcePoller::read_data_source()
      to bound worker blocking on slow/failing remote endpoints (previously used WP default
      of 5s with no upper bound enforcement).
    - Remove redundant read_specs_from_data_sources() call in Events::do_wc_admin_daily()
      for RemoteInboxNotifications; RemoteInboxNotificationsEngine::run() already calls
      get_specs_from_data_sources() which fetches only when the transient is expired.
    - Swap read_specs_from_data_sources() -> get_specs_from_data_sources() in
      Events::possibly_refresh_data_source_pollers() for PaymentGatewaySuggestions and
      RemoteFreeExtensions pollers so HTTP calls are skipped when transients are fresh.
    - Remove now-unused RemoteInboxNotificationsDataSourcePoller import from Events.php.

    * Changelog

    * Potential fix for pull request finding

    Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

    * Potential fix for pull request finding

    Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

    * Set transient_expiry to DAY_IN_SECONDS for RemoteInboxNotificationsDataSourcePoller to preserve daily refresh

    Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

    * Address review feedback: filter for timeout and schedule wc_admin_daily at 3am

    - Add woocommerce_data_source_poller_timeout filter so site owners/extensions can adjust the HTTP timeout
    - Schedule wc_admin_daily_wrapper at tomorrow_3am (consistent with cleanup_logs and cleanup_rate_limits) to reduce timeout impact during business hours

    Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

    * Address review feedback: sanitize timeout filter, fix @since, skip empty transient on fetch failure

    Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

    ---------

    Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
    Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/fix-wc-admin-daily-timeout-and-transient-guard b/plugins/woocommerce/changelog/fix-wc-admin-daily-timeout-and-transient-guard
new file mode 100644
index 00000000000..6974d9ed2af
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-wc-admin-daily-timeout-and-transient-guard
@@ -0,0 +1,4 @@
+Significance: patch
+Type: performance
+
+Add 3s timeout to DataSourcePoller HTTP calls and skip redundant remote fetches in wc_admin_daily when transients are fresh.
diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php
index c7e83ce127c..ed5057f8966 100644
--- a/plugins/woocommerce/includes/class-woocommerce.php
+++ b/plugins/woocommerce/includes/class-woocommerce.php
@@ -1661,7 +1661,7 @@ final class WooCommerce {

 		as_schedule_recurring_action( $tomorrow_3am, DAY_IN_SECONDS, 'woocommerce_cleanup_rate_limits_wrapper', array(), 'woocommerce', true );

-		as_schedule_recurring_action( time(), DAY_IN_SECONDS, 'wc_admin_daily_wrapper', array(), 'woocommerce', true );
+		as_schedule_recurring_action( $tomorrow_3am, DAY_IN_SECONDS, 'wc_admin_daily_wrapper', array(), 'woocommerce', true );

 		// Note: this is potentially redundant when the core package exists.
 		as_schedule_single_action( time() + 10, 'generate_category_lookup_table_wrapper', array(), 'woocommerce', true );
diff --git a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsDataSourcePoller.php b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsDataSourcePoller.php
index 414396e878a..4580948672a 100644
--- a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsDataSourcePoller.php
+++ b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsDataSourcePoller.php
@@ -42,7 +42,8 @@ class RemoteInboxNotificationsDataSourcePoller extends DataSourcePoller {
 				self::ID,
 				self::get_data_sources(),
 				array(
-					'spec_key' => 'slug',
+					'spec_key'         => 'slug',
+					'transient_expiry' => DAY_IN_SECONDS,
 				)
 			);
 		}
diff --git a/plugins/woocommerce/src/Admin/RemoteSpecs/DataSourcePoller.php b/plugins/woocommerce/src/Admin/RemoteSpecs/DataSourcePoller.php
index d0812fd5ffa..00762fdeea4 100644
--- a/plugins/woocommerce/src/Admin/RemoteSpecs/DataSourcePoller.php
+++ b/plugins/woocommerce/src/Admin/RemoteSpecs/DataSourcePoller.php
@@ -174,6 +174,10 @@ abstract class DataSourcePoller {
 			$this->merge_specs( $specs_from_data_source, $specs, $url );
 		}

+		if ( count( $specs ) === 0 ) {
+			return false;
+		}
+
 		$specs_group            = get_transient( $this->args['transient_name'] );
 		$specs_group            = is_array( $specs_group ) ? $specs_group : array();
 		$locale                 = get_user_locale();
@@ -183,7 +187,7 @@ abstract class DataSourcePoller {
 			$specs_group,
 			$this->args['transient_expiry']
 		);
-		return count( $specs ) !== 0;
+		return true;
 	}

 	/**
@@ -226,6 +230,13 @@ abstract class DataSourcePoller {
 				$url
 			),
 			array(
+				/**
+				 * Filters the HTTP timeout (in seconds) used when fetching remote specs data sources.
+				 *
+				 * @since 10.8.0
+				 * @param int $timeout Timeout in seconds. Default 3.
+				 */
+				'timeout'    => max( 1, absint( apply_filters( 'woocommerce_data_source_poller_timeout', 3 ) ) ),
 				'user-agent' => 'WooCommerce/' . WC_VERSION . '; ' . home_url( '/' ),
 			)
 		);
diff --git a/plugins/woocommerce/src/Internal/Admin/Events.php b/plugins/woocommerce/src/Internal/Admin/Events.php
index da0923e6613..7b0f1ee6831 100644
--- a/plugins/woocommerce/src/Internal/Admin/Events.php
+++ b/plugins/woocommerce/src/Internal/Admin/Events.php
@@ -8,7 +8,6 @@ namespace Automattic\WooCommerce\Internal\Admin;
 defined( 'ABSPATH' ) || exit;

 use Automattic\WooCommerce\Admin\Features\Features;
-use Automattic\WooCommerce\Admin\RemoteInboxNotifications\RemoteInboxNotificationsDataSourcePoller;
 use Automattic\WooCommerce\Admin\RemoteInboxNotifications\RemoteInboxNotificationsEngine;
 use Automattic\WooCommerce\Internal\Admin\Notes\CustomizeStoreWithBlocks;
 use Automattic\WooCommerce\Internal\Admin\Notes\CustomizingProductCatalog;
@@ -143,7 +142,6 @@ class Events {
 		$this->possibly_refresh_data_source_pollers();

 		if ( $this->is_remote_inbox_notifications_enabled() ) {
-			RemoteInboxNotificationsDataSourcePoller::get_instance()->read_specs_from_data_sources();
 			RemoteInboxNotificationsEngine::run();
 		}

@@ -253,7 +251,8 @@ class Events {
 	}

 	/**
-	 *   Refresh transient for the following DataSourcePollers on wc_admin_daily cron job.
+	 *   Prime or fetch specs for the following DataSourcePollers on the wc_admin_daily cron job
+	 *   when their related transients are missing or expired:
 	 *   - PaymentGatewaySuggestionsDataSourcePoller
 	 *   - RemoteFreeExtensionsDataSourcePoller
 	 */
@@ -261,11 +260,11 @@ class Events {
 		$completed_tasks = get_option( 'woocommerce_task_list_tracked_completed_tasks', array() );

 		if ( ! in_array( 'payments', $completed_tasks, true ) && ! in_array( 'woocommerce-payments', $completed_tasks, true ) ) {
-			PaymentGatewaySuggestionsDataSourcePoller::get_instance()->read_specs_from_data_sources();
+			PaymentGatewaySuggestionsDataSourcePoller::get_instance()->get_specs_from_data_sources();
 		}

 		if ( ! in_array( 'store_details', $completed_tasks, true ) && ! in_array( 'marketing', $completed_tasks, true ) ) {
-			RemoteFreeExtensionsDataSourcePoller::get_instance()->read_specs_from_data_sources();
+			RemoteFreeExtensionsDataSourcePoller::get_instance()->get_specs_from_data_sources();
 		}
 	}
 }