2014-05-09 12 views
7

Sto provando a utilizzare le transazioni Django 1.6 per evitare le condizioni di gara in un gioco che sto sviluppando. Il server di gioco ha un obiettivo semplice: abbinare due giocatori.Django 1.6 transazioni per evitare condizioni di gara

Il mio approccio attuale è:

  1. utente vuole giocare
  2. I server controlla se c'è qualcun altro in attesa di giocare.
    1. Se c'è non è, si crea un oggetto GameConnection (che ha un identificatore univoco - uuid4).
    2. Se è, ottiene l'identificatore GameConnection ed elimina la GameConnection.

Questo è il codice:

# data['nickname'] = user's choice 
games = GameConnection.objects.all() 
if not games: 
    game = GameConnection.objects.create(connection=unicode(uuid.uuid4())) 
    game.nick1 = data["nickname"] 
    game.save() 

    response = HttpResponse(json.dumps({'connectionId': game.connection, 'whoAmI': 1, 'nick1': game.nick1, 'nick2': ""})) 
else: 
    game = games[0] 
    conn = game.connection 
    nick1 = game.nick1 
    nick2 = data["nickname"] 
    game.delete() 
    response = HttpResponse(json.dumps({'connectionId': conn, 'whoAmI': 2, 'nick1': nick1, 'nick2': nick2})) 

return response 

Ovviamente c'è una condizione di competizione sul codice di cui sopra. Poiché questo codice non è atomico, può succedere che:

  • Un controllo per le connessioni di gioco. Non ne trova.
  • A crea una connessione di gioco.
  • B controlla le connessioni di gioco. Trova uno (A).
  • C controlla le connessioni di gioco. Trova uno (A).
  • B ottiene l'identificatore di connessione di A e avvia una partita.
  • C ottiene l'identificatore di connessione di A e avvia una partita.

Ho provato fare ma tutto questo blocco sotto with transaction.atomic():, o per utilizzare la @transaction.atomic decoratore. Ma ancora, sono in grado di riprodurre le condizioni della gara.

Sono sicuro che c'è qualcosa nella dinamica delle transazioni che mi manca qui. Qualcuno può far luce?

+0

Non sono un esperto, ma secondo la mia comprensione, le transazioni atomiche si applicano per le scritture. Nel tuo caso, potresti voler utilizzare qualche tipo di blocco mutex per garantire che avvenga un solo accoppiamento alla volta. Questo è simile a @synchronized in Java. Però non è efficiente a tutti i livelli di rendimento. –

risposta

2

@Sai è sulla traccia ... la chiave è che il blocco/mutex non si verificherà fino a una scrittura (o eliminazione). Come codificato, ci sarà sempre un intervallo tra "discovery" (lettura) della connessione in sospeso e "claim" (write/lock) della connessione in sospeso, senza alcun modo per sapere che una connessione è in fase di richiesta.

Se si utilizza PostgreSQL (abbastanza sicuro MySQL supporta, anche), è possibile forzare la serratura con "selezionare per l'aggiornamento", che impedirà una nuova richiesta di ottenere la stessa riga fino al completamento della transazione:

game = GameConnection.objects.all()[:1].select_for_update() 
if game: 
    #do something, update, delete, etc. 
else: 
    #create 

Nota finale - considerare qualcosa di diverso da all() in modo esplicito su quale gioco può essere prelevato (ad es., Ordine da un timestamp "creato" o qualcosa del genere). Spero possa aiutare.