Automattic\WooCommerce\Internal\BatchProcessing

BatchProcessingController::mutate_enqueued_processorsprivateWC 11.0.0

Run a read-modify-write of the enqueued-processors option inside a short-lived critical section.

The list is stored in a single option and mutated by several code paths (including the per-request 'shutdown' enqueue from continuous HPOS background sync), so an unguarded read-modify-write can lose updates under concurrency: two requests read the same list, each writes its own version, and the later write silently drops the other's change. This serializes those mutations with a self-expiring options-table mutex and re-reads the freshest persisted value inside the lock so the mutator always operates on current state.

The lock is best-effort: if it cannot be acquired within ENQUEUED_PROCESSORS_LOCK_ATTEMPTS tries the mutation still proceeds, which is no worse than the previous lock-free behavior. The fresh re-read inside the section narrows the race window even when the lock provides no real exclusion, and the watchdog reconciles any residual divergence on its next run.

Method of the class: BatchProcessingController{}

No Hooks.

Returns

Array. The list as persisted (the mutator's return value).

Usage

// private - for code of main (parent) class only
$result = $this->mutate_enqueued_processors( $mutator ): array;
$mutator(callable) (required)
Receives the current list (array of class-name strings) and returns the new list.

Changelog

Since 11.0.0 Introduced.

BatchProcessingController::mutate_enqueued_processors() code WC 11.0.1

private function mutate_enqueued_processors( callable $mutator ): array {
	$lock_token = $this->acquire_enqueued_processors_lock();
	try {
		/*
		 * Drop any request-cached copy so the read below reflects writes committed by concurrent requests
		 * that landed after this request first read the option. Both the per-option entry AND the shared
		 * 'notoptions' entry must be cleared: when the option does not yet exist (first enqueue, or after a
		 * corrupted option was deleted), get_option() short-circuits on a stale 'notoptions' hit and would
		 * otherwise return the default empty list even though a concurrent request just created the row.
		 */
		wp_cache_delete( self::ENQUEUED_PROCESSORS_OPTION_NAME, 'options' );
		wp_cache_delete( 'notoptions', 'options' );
		$current = $this->get_enqueued_processors();
		$updated = $mutator( $current );
		if ( $updated !== $current ) {
			$this->set_enqueued_processors( $updated );
		}
		return $updated;
	} finally {
		if ( null !== $lock_token ) {
			$this->release_enqueued_processors_lock( $lock_token );
		}
	}
}