Data Consistency on very close events in Firebase

2019-06-01 00:29发布

I am building an iOS game. Each game room is constructed from two users. After two users get matched, I'm displaying a "waiting for respond" timer on both devices: they need to click am "I'm Ready" button within 8 second or both of them will get kicked from the room + deleted from Firebase.

The correct state of the users (before each of them clicked on the "I'm Ready Button"):

Parent
      Matches
             User 1
                   opponent : User2
                   state : "NotReady"
             User 2
                   opponent : User1
                   state : "NotReady"

Critical note - The timer on both devices time difference is +-2 seconds, In other words - once device timer is going to end before the other one

When a user press the "I'm Ready" button i'm updating the state : "userReady",and check if the other user is ready too (observing its state value).

If both users are userReady - game on.

PROBLEM

so we have already cleared that in 100% of the cases i have a small time difference between both devices. BUT, if for instance ,

User1 clicked I'm Ready button. So now User2 got an ChildUpdate event, and as far as he knows - User2 is completely ready to play.

User1 timer is ending first(fact), so when his timer will finish, User2 timer will remain 1 seconds. NOW, on User1 time just reached zero, so he get kicked out of the room, and send a removeValue event on each of the Users nodes. While this is happening, at this very small "gap",(between the zero time of User1 timer ending,User2 clock show's 1 sec(fact) - and he press the ready button. than he thinks that User1 is ready to play, and he as well - than the game starts playing.

Final Results -

Both players got deleted from Firebase

User1 is out of the room

User2 starts the game(and he think he is an opponent)

How can i solve this end-case scenario?, I have already tried calling startGame function only when the "UpdateChild state" is done, but it still gets in, maybe because the order for updateChild,and removeValue"?

Any suggestions? And BIG thank you Firebase team for reaching how all the time!!!

1条回答
何必那么认真
2楼-- · 2019-06-01 01:05

It doesn't make sense that User 1 would accept and then expire anyway. User 2 should be the one expiring if the time limit is reached and he hasn't accepted yet.

To prevent this, you're looking for transactions. When you would "expire" a user, use a transaction to do so, so that there are no data conflicts.

var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");

ref.child('Parent/Matches').child(user1).transaction(function(currentValue) {
    if( currentValue && currentValue.state === 'NotReady' ) {
       return null; // delete the user
    }
    else {
       return undefined; // abort the transaction; status changed while we were attempting to remove it
    }
});

Possibly correct in swift:

var ref = Firebase(url: "https://<YOUR-FIREBASE-APP>.firebaseio.com/Parent/Matches/<user id>")

upvotesRef.runTransactionBlock({
    (currentData:FMutableData!) in
    if currentData && currentData.value["state"] != "NotReady" {
        return FTransactionResult.successWithValue(NSNull)
    }
    return FTransactionResult.abort();
});

Furthermore, you could simplify things and reduce the opportunity for chaos by not deleting/expiring the records until both users have reached their accept time limit, and then doing both at once in a transaction.

查看更多
登录 后发表回答