最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

php - How to handle simultaneous AJAX requests which update the same metadata? - Stack Overflow

programmeradmin2浏览0评论

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 );
    }
}

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 );
    }
}
Share Improve this question edited Feb 15 at 18:06 Power asked Feb 15 at 12:47 PowerPower 1138 bronze badges 1
  • You need to serialize the parallel requests with a queue – Honk der Hase Commented Feb 15 at 13:07
Add a comment  | 

2 Answers 2

Reset to default 2

You are always overwriting the meta-data entry.

How to solve this confusing problem?

Do not overwrite. The meta-data can contain the same key multiple times. That is what the $single parameter is for. You have it set to true, that signals you want to operate on a single value. As you could demonstrate, this leads to the last-man-standing resolution, that is the last AJAX call (write) wins.

The default for the $single parameter is false. It will return an array then:

assert( is_scalar($post_id) );

$user_id = get_current_user_id( );
$key     = 'saved';
$saved   = get_user_meta( $user_id, $key );

And only if it was not yet added (to not have too many duplicate meta values), add it:

if ( false === in_array( $post_id, $saved ) )
{
    add_user_meta( $user_id, $key, $post_id );
}

Now the procedure is appending. That is you need to add a bit more data-handling, e.g. cleaning up the list every night or so. This depends on your requirements.

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;
}
发布评论

评论列表(0)

  1. 暂无评论