question

Daxay avatar image
Daxay asked

Prevent concurrent requests in cloudscript

I want to prevent concurrency on cloudscript functions call.

For example, I created one cloudscript function name claimDailyRewards, which gives users a daily reward item in their inventory.

Now, if one user tries to cheat and call this function many times concurrently by using rest API, then that user can get multiple rewards. I want to prevent this to happen.

I think it would be great if this can be manage with some sort of key value pair where you can update and get old value in return in same API call.

For example,

Suppose user A tries to cheat and call function multiple time concurrently.

In cloudscript function, i will first call update method and set it to true and it gives me undefined value in response.
So for 1st request it will mark dailRewardClaim key to true. and continue its execution

meanwhile 2nd concurrent request come, it will mark dailyRewardClaim to true but in response it will get true value as 1st request already updated it. so it return error.

Now, 1st request finished its execution and delete dailyRewardClaim key.



This way we can prevent concurrency easily. I am not sure about this so i asked question here and not created any feature request.

Is it possible? if not then is there any other way?


Thanks :).

Player DataCloudScript
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Sarah Zhang avatar image
Sarah Zhang answered

Thanks for the patience, and thanks for sharing. After researching, we can confirm that PlayFab doesn’t provide such features that returning the last previous data in the response of Update User Data API calls. These API methods that Get User InternalData/Read-OnlyData/Data can’t retrieve the previous DataVersion too.

For your case, could you consider using another design to implement the “Daily Reward”? PlayFab Currency has the “Recharge rate” and “Recharge maximum” properties. If you set the “Recharge rate” as one and set “Recharge maximum” as 1 too. It would mean the corresponding type of virtual currency would regenerate 1 unit per day, and the player can bank up to 1 unit at most. Only when the currency has been used up, and one day has passed, the currency will be regenerated. You can let the player purchase the reward item using this type of currency. It would be a reliable way to implement the “Daily Reward”. Our sample – https://github.com/PlayFab/PlayFab-Samples/tree/master/Recipes/PrizeWheel uses this design, you can refer to it for more details.

If the feature that returning the last previous data in response when updating new one is still necessary for you, you can try to add a feature-request for it.

4 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Daxay avatar image Daxay commented ·

Ok, thanks.

I already created feature request for this.

Actually, I just give you one example of where this can be use. And you provided better solution.

But what to do to prevent concurrency in cloudscript?
As its serious issue. Someone who want to cheat can call cloudscript function concurrently and get rewards multiple times. And we cant do anything about it.

0 Likes 0 ·
Sarah Zhang avatar image Sarah Zhang Daxay commented ·

PlayFab CloudScript can use some method to prevent the clients from executing the functions and only allow the PlayStreamEvent to trigger them. You can refer to this thread -- https://community.playfab.com/questions/43950/restrict-access-to-certain-cloudscripts.html for more details.

Besides, you can also set a cooldown time to avoid clients frequently calling the function to update the Internal/ReadOnly API method. The logic could be something like this function processPlayerMove -- https://github.com/PlayFab/CloudScriptSamples/blob/master/BasicSample/basic_sample.js#L172. You can find it in the default revision. Get Internal/Read-Only Data API also returns LastUpdated timestamp and data's DataVersion. You can use them to do the corresponding checks.

0 Likes 0 ·
Daxay avatar image Daxay Sarah Zhang commented ·

But any of these solution cant prevent concurrent requests

For 1st solution, we cant do that since we want that function to be triggered by user its self.

For 2nd solution, I am using this solution, but it only works if user request one by one. If user call cloudscript function concurrently multiple times then it can't prevent and gives rewards to user multiple times.

Thanks :)

0 Likes 0 ·
Show more comments
Sarah Zhang avatar image
Sarah Zhang answered

Your solution is feasible. You can store the dailRewardClaim K/V pair as Internal Player Data. Internal Player Data is player data that is only available to your server. You can refer to this documentation -- How to modify read-only or internal player data from CloudScript for the tutorial of modifying internal player data from CloudScript. For clarification, you can’t update the Internal Player Data and get it using the same PlayFab API. It would be feasible if you use this server API UpdateUserInternalData create, update, or delete the data and use this server API GetUserInternalData to read it for the player. You can use these two API methods in the CloudScript function claimDailyRewards to achieve your requirements.

5 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Daxay avatar image Daxay commented ·

Hi, @Sarah Zhang, thanks for the answer.

I already implemented this using userReadOnlyData.

Infect i am reading then validate data and then remove that key from user internal data on next line.

Look at the below code, its just a few lines from the claimDailyRewards function.

const combinedData = server.GetPlayerCombinedInfo({
  PlayFabId: currentPlayerId,
  InfoRequestParameters: {
      GetUserReadOnlyData: true,
      UserReadOnlyDataKeys: [UserReadonlyDataKeys.DAILY_REWARD_STATS],
      GetUserVirtualCurrency: true,
  },
}).InfoResultPayload;
const userReadonlyData = combinedData.UserReadOnlyData;
const userBalance = combinedData.UserVirtualCurrency;

if (!userReadonlyData[UserReadonlyDataKeys.DAILY_REWARD_STATS]) {
  throw new Error('You are not yet eligible to claim reward');
}
server.UpdateUserReadOnlyData({
  PlayFabId: currentPlayerId,
  KeysToRemove: [UserReadonlyDataKeys.DAILY_REWARD_STATS],
});

But this implementation didn't prevent concurrent issue.

If you have different suggestion then i am happy to implement.

0 Likes 0 ·
harsh avatar image harsh Daxay commented ·

To make things clear, OP's problem is that the player can send hundreds of claimDailyRewards requests within the timespan that the first call gets/sets the KVP in internal data. Essentially, they want a semaphore for accessing internal data.

1 Like 1 ·
Daxay avatar image Daxay harsh commented ·

Yes, exactly. As i don't think its too hard. As playfab already reject multiple concurrent update user data calls.

I am just asking for a feature that can give you old data in response when updating new one.

So do you think its feasible?

0 Likes 0 ·
Show more comments
harsh avatar image
harsh answered

https://redis.io/topics/distlock

Please take a look at this.

It's not ideal for real-time, as it adds processing time greater than round-trip time to retrieving a lock. I'm proposing that you setup distributed locks on your own server. That way, you can ensure only one call to claimRewards runs at a time.

1 comment
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Daxay avatar image Daxay commented ·

Yeah, but it would be great if i don't need to rely on 3rd party for just simple feature.

0 Likes 0 ·
Markus Henschel avatar image
Markus Henschel answered

I'm running into the same issue here. I'm wondering if instead of player data you could use entity objects. Those seem to have the feature of preventing concurrent access by rejecting it if the ProfileVersion returned by GetObjects doesn't match ExpectedProfileVersion in SetObjects.

https://docs.microsoft.com/en-us/rest/api/playfab/data/object/getobjects

https://docs.microsoft.com/en-us/rest/api/playfab/data/object/setobjects

The drawback is that those objects are quite limited in size. 5 objects with 1000 bytes in size for the json data isn't a lot. I guess those objects are in a more expansive storage that might even have to stay in memory or something.

Maybe you could also use those objects to implement some kind of lock. Like putting an entity object with a bool flag into the player and trying to set it to true before modifying player data. Then set it to false again after the modifications to player data have finished.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.