Skip to content

Only release claims on pending actions. #1248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion classes/ActionScheduler_wcSystemStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ protected function get_action_status_date( $status, $date_type = 'oldest' ) {

$action = $this->store->query_actions(
array(
'claimed' => false,
'status' => $status,
'per_page' => 1,
'order' => $order,
Expand Down
1 change: 0 additions & 1 deletion classes/WP_CLI/System_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,6 @@ protected function get_action_status_date( $status, $date_type = 'oldest' ) {
$order = 'oldest' === $date_type ? 'ASC' : 'DESC';

$args = array(
'claimed' => false,
'status' => $status,
'per_page' => 1,
'order' => $order,
Expand Down
11 changes: 9 additions & 2 deletions classes/data-stores/ActionScheduler_DBStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -1094,7 +1094,7 @@ public function find_actions_by_claim_id( $claim_id ) {
}

/**
* Release actions from a claim and delete the claim.
* Release pending actions from a claim and delete the claim.
*
* @param ActionScheduler_ActionClaim $claim Claim object.
* @throws \RuntimeException When unable to release actions from claim.
Expand All @@ -1107,14 +1107,21 @@ public function release_claim( ActionScheduler_ActionClaim $claim ) {
*/
global $wpdb;

if ( 0 === intval( $claim->get_id() ) ) {
// Verify that the claim_id is valid before attempting to release it.
return;
}

/**
* Deadlock warning: This function modifies actions to release them from claims that have been processed. Earlier, we used to it in a atomic query, i.e. we would update all actions belonging to a particular claim_id with claim_id = 0.
* While this was functionally correct, it would cause deadlock, since this update query will hold a lock on the claim_id_.. index on the action table.
* This allowed the possibility of a race condition, where the claimer query is also running at the same time, then the claimer query will also try to acquire a lock on the claim_id_.. index, and in this case if claim release query has already progressed to the point of acquiring the lock, but have not updated yet, it would cause a deadlock.
*
* We resolve this by getting all the actions_id that we want to release claim from in a separate query, and then releasing the claim on each of them. This way, our lock is acquired on the action_id index instead of the claim_id index. Note that the lock on claim_id will still be acquired, but it will only when we actually make the update, rather than when we select the actions.
*
* We only release pending actions in order for them to be claimed by another process.
*/
$action_ids = $wpdb->get_col( $wpdb->prepare( "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d", $claim->get_id() ) );
$action_ids = $wpdb->get_col( $wpdb->prepare( "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d AND status = %s", $claim->get_id(), self::STATUS_PENDING ) );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm that this array is empty and the below query to set the claim_id = 0 does not get run. However, my claims in the DB are still getting set to 0.

Is there anywhere else this might be happening or is it possible for multiple instances of AS to be running at once?

Copy link
Member

@jorgeatorres jorgeatorres Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that actions processed via Tools > Scheduled Actions are not claimed/released. Is this how you're running the actions @joshuatf?

For the runner to properly stake a claim and then release the actions, you'd have to either let those actions run naturally or use a runner such as the CLI one (i.e. run wp action-scheduler run).


$row_updates = 0;
if ( count( $action_ids ) > 0 ) {
Expand Down
2 changes: 1 addition & 1 deletion classes/data-stores/ActionScheduler_HybridStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ public function get_claim_id( $action_id ) {
}

/**
* Release a claim in the table data store.
* Release a claim in the table data store on any pending actions.
*
* @param ActionScheduler_ActionClaim $claim Claim object.
*/
Expand Down
29 changes: 23 additions & 6 deletions classes/data-stores/ActionScheduler_wpPostStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -791,25 +791,42 @@ public function find_actions_by_claim_id( $claim_id ) {
}

/**
* Release claim.
* Release pending actions from a claim.
*
* @param ActionScheduler_ActionClaim $claim Claim object to release.
* @return void
* @throws RuntimeException When the claim is not unlocked.
*/
public function release_claim( ActionScheduler_ActionClaim $claim ) {
$action_ids = $this->find_actions_by_claim_id( $claim->get_id() );
if ( empty( $action_ids ) ) {
return; // nothing to do.
}
$action_id_string = implode( ',', array_map( 'intval', $action_ids ) );
/**
* Global wpdb object.
*
* @var wpdb $wpdb
*/
global $wpdb;

$claim_id = $claim->get_id();
if ( trim( $claim_id ) === '' ) {
// Verify that the claim_id is valid before attempting to release it.
return;
}

// Only attempt to release pending actions to be claimed again. Running and complete actions are no longer relevant outside of admin/analytics.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$action_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT ID, post_date_gmt FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s AND post_status = %s",
self::POST_TYPE,
$claim_id,
self::STATUS_PENDING
)
);

if ( empty( $action_ids ) ) {
return; // nothing to do.
}
$action_id_string = implode( ',', array_map( 'intval', $action_ids ) );

// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$result = $wpdb->query(
$wpdb->prepare(
Expand Down