Page 1 of 1

Rollback on mysql_deadlock_restart_transaction

Posted: 20 March 2021, 15:01
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

Re: Rollback on mysql_deadlock_restart_transaction

Posted: 20 March 2021, 20:51
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.

Re: Rollback on mysql_deadlock_restart_transaction

Posted: 20 March 2021, 22:37
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?

Re: Rollback on mysql_deadlock_restart_transaction

Posted: 21 March 2021, 15:17
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?

Re: Rollback on mysql_deadlock_restart_transaction

Posted: 21 March 2021, 15:36
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.

Re: Rollback on mysql_deadlock_restart_transaction

Posted: 22 March 2021, 11:08
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).

Re: Rollback on mysql_deadlock_restart_transaction

Posted: 22 March 2021, 13:54
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.