In my Wordpress site, each user has a meta data key (i.e., "saved") which contains a list of post IDs stored as an array. The list is updated everytime the user clicks the "save" button of a post. The saving process is done using AJAX request.
The problem is that when I click the "save" buttons of multiple posts at the same time (without waiting each request to finish), I find that only the post ID of last clicked button is added to the list, despite that the other buttons have properly finished their requests without errors.
How to solve this confusing problem?
below is the php function
function add_postID_to_user_saved_list($post_id) {
$saved = get_user_meta( get_current_user_id(), 'saved', true );
if ( empty($saved) ) {
$saved = [$post_id];
} else {
$saved[] = $post_id;
}
if ( !update_user_meta( get_current_user_id(), 'saved', $saved ) ) {
add_user_meta( get_current_user_id(), 'saved', $saved, true );
}
}
I found a solution that uses transient locks for the meta keys in progress.
function add_postID_to_user_saved_list($post_id) {
$user_id = get_current_user_id();
$keys = array(
'saved'.$user_id,
);
if ( !lock_keys( $keys ) ) {
return false;
}
$saved = get_user_meta( $user_id, 'saved', true );
if ( empty($saved) ) {
$saved = [$post_id];
} else {
$saved[] = $post_id;
}
if ( !update_user_meta( $user_id, 'saved', $saved ) ) {
add_user_meta( $user_id, 'saved', $saved, true );
}
unlock_keys( $keys );
}
// Used before updating meta keys to check whether they are locked or not (and lock them if not) to avoid classic race conditions
function lock_keys( $keys ) {
if ( !is_array( $keys ) || empty( $keys ) ) {
// "No meta keys have been specified to lock!"
return false;
}
$attempts_limit = 3;
$attempts_interval = 1;
$attempt = 0;
while ( $attempt < $attempts_limit ) {
$attempt++;
$locked_keys = get_transient( 'locked_keys' );
if ( !is_array( $locked_keys ) ) {
$locked_keys = array();
}
// Check if another request is currently updating any of the meta keys
if ( !empty( array_intersect( $keys, $locked_keys ) ) ) {
// Another process is already updating, wait for a short period and retry
sleep( $attempts_interval );
continue;
}
// Set transient locks for the meta keys to indicate that an update is in progress
set_transient( 'locked_keys', array_merge( $locked_keys, $keys ), 15 ); // secs must be > duration of the longest update process
return true;
}
// All attempts are exhausted in vain, notify the user and handle the failure
// "The server is busy! Please try again in a few seconds."
return false;
}
// Used after updating meta keys to release the transient lock off them and to indicate that the update request is complete allowing other requests to update them
function unlock_keys( $keys ) {
if ( !is_array( $keys ) || empty( $keys ) ) {
// "No meta keys have been specified to unlock!"
return false;
}
$locked_keys = get_transient( 'locked_keys' );
if ( !is_array( $locked_keys ) ) {
$locked_keys = array();
}
set_transient( 'locked_keys', array_diff( $locked_keys, $keys ), 15 ); // secs must be > duration of the longest update process
return true;
}