Rollback on mysql_deadlock_restart_transaction

Game development with Board Game Arena Studio
Post Reply
User avatar
quietmint
Posts: 299
Joined: 31 July 2017, 00:28

Rollback on mysql_deadlock_restart_transaction

Post by quietmint »

I need to understand more about transaction rollback.

I have a bug where some exception occurs (in this case, a SQL deadlock exception, but really it should not matter) and the BGA framework does not properly clean up. From the stack trace, you can see the ajax action initiated by the user causes a transition to state #90. Then it runs stCleanup() (which is 'action' for state #90) where something goes wrong. In case of unexpected exception, the BGA framework should do a full transaction roll back and cancel all the changes in the stack -- aka, it should undo the transition to state #90 -- but that is not happening. Instead, my game is left in limbo in state #90, and the user is unable to do anything. I do not care about fixing the deadlock itself -- occasional deadlocks is inevitable and the solution is just to retry -- but how do I deal with this broken exception handling behavior? Because the BGA framework does not undo everything, the user is not taken back to the point before they clicked, so they are not given the option to retry.

Code: Select all

20/03 06:41:29 [error] [T157099380] [85702250/thomas3633] Unexpected exception: mysql_deadlock_restart_transaction (BGA service error 1616218889)
#0 /var/tournoi/release/games/hardback/210315-0450/modules/CardMgr.class.php(442): APP_DbObject::DbQuery('UPDATE card SET...')
#1 /var/tournoi/release/games/hardback/210315-0450/modules/CardMgr.class.php(648): CardMgr::moveHandToTableau(85528055)
#2 /var/tournoi/release/games/hardback/210315-0450/hardback.game.php(1256): CardMgr::reset(85702250)
#3 /var/tournoi/release/tournoi-210316-1128/www/game/module/table/gamestate.game.php(469): hardback->stCleanup()
#4 /var/tournoi/release/tournoi-210316-1128/www/game/module/table/gamestate.game.php(365): Gamestate->jumpToState(90)
#5 /var/tournoi/release/games/hardback/210315-0450/hardback.game.php(1208): Gamestate->nextState('next')
#6 /var/tournoi/release/games/hardback/210315-0450/hardback.action.php(193): hardback->skip()
#7 /var/tournoi/release/tournoi-210316-1128/www/include/webActionCore.inc.php(189): action_hardback->skipPurchase()
#8 /var/tournoi/release/tournoi-210316-1128/www/index.php(247): launchWebAction('hardback', 'action_hardback', 'skipPurchase', false, false, NULL, true, false)
#9 {main}
http://boardgamearena.com/8/hardback/hardback/skipPurchase.html?lock=ea31be09-4f1b-4a33-84a2-c563e2838f72&table=157099380&dojo.preventCache=1616218889469
User avatar
bidulpik
Posts: 79
Joined: 17 November 2011, 09:02

Re: Rollback on mysql_deadlock_restart_transaction

Post by bidulpik »

Do you have to have this rollback action made by all players ?
I had this error when I trigger an Ajax action that modify DB for several players.
User avatar
quietmint
Posts: 299
Joined: 31 July 2017, 00:28

Re: Rollback on mysql_deadlock_restart_transaction

Post by quietmint »

Not sure I understand your question. In this case, one player is active and doing an action. Another non-active player might try a different action at the same moment (I think any two simultaneous requests can cause a deadlock, due to framework internals). The non-active player is allowed to take an action in my game. Here in this example the failed thing is the active player's request. The active player's request is not rolling back.

---

If your ask why we need rollback: Yes, it is absolutely necessary that requests are atomic: BGA framework must do everything or nothing (e.g., a complete rollback of the request), never a partial execution. Otherwise, the game is left in a broken state where it's impossible for the game developer to deal with the problem. I have no way to detect if this happened, let alone figure out which parts of my transaction got committed vs. which parts were discarded. The state machine is completely useless without this guarantee. Why have states at all if they don't mean anything and can transition "at random" like this?
User avatar
Victoria_La
Posts: 665
Joined: 28 December 2015, 20:55

Re: Rollback on mysql_deadlock_restart_transaction

Post by Victoria_La »

We had this conversation on chat but baically BGA framework will rolls back all failed transations, the pseudo code is like this

Code: Select all

try {
  startTransaction();
  game->action();
} catch (Exception $exception) {
  rollback();
  trace( "DB rollback: action cancelled" );
  throw $exception;
}
commit();
trace( "DB commit: action validate" )
It more complex because it has retry count as well, but if the fact that you have this exception logged means it did not do retry
Do you have any code of you own game that play with db locking, retry count, transations, etc directly?
User avatar
quietmint
Posts: 299
Joined: 31 July 2017, 00:28

Re: Rollback on mysql_deadlock_restart_transaction

Post by quietmint »

Nope, I do not have any code that is trying to do manual database transaction, locking, commit, etc.

If the code is as you say, then BGA framework must be doing extra commit of the transaction at some point (which prevent the full rollback) or the rollback function is bad.
User avatar
Een
Posts: 3861
Joined: 16 June 2010, 19:52

Re: Rollback on mysql_deadlock_restart_transaction

Post by Een »

From your description I understand that non active players can play anytime, and that when they play, it changes the current state. This looks loke a tricky case.

I suppose that if multiple requests happen at the same time, if one goes through it can make a commit, even if others are deadlocked at the same time. If you want to avoid that, maybe you should have some kind of concurrency management for this specific case (that's actually analogous to multiplayer states, where we make sure that we change state only when everyone has finished playing the current state).
User avatar
quietmint
Posts: 299
Joined: 31 July 2017, 00:28

Re: Rollback on mysql_deadlock_restart_transaction

Post by quietmint »

Een wrote: 22 March 2021, 11:08 From your description I understand that non active players can play anytime, and that when they play, it changes the current state. This looks loke a tricky case.

I suppose that if multiple requests happen at the same time, if one goes through it can make a commit, even if others are deadlocked at the same time. If you want to avoid that, maybe you should have some kind of concurrency management for this specific case (that's actually analogous to multiplayer states, where we make sure that we change state only when everyone has finished playing the current state).
No. The inactive player can play any time, but never causes state transition. The inactive player is just drawing a card. So it uses notifyAllPlayers, but it does nothing about state transition.

Those two are separate web requests. So I expect one of them could fail if same exact second (inactive player or active player). Whichever falls, the whole thing should rollback and one person should get "please try again" whatever error message.

Only the active player is doing a state transition.

Say inactive player's draw card action wins the deadlock and gets committed. Why does it affect active player's state transition? When active player request is rolled back, that should roll back the state transition as well, right? Above stack trace error log is for the active player. You can see in the stack trace the state transition.
Post Reply

Return to “Developers”