2014-05-09 8 views
7

Ich versuche, Django 1.6 Transaktionen zu verwenden, um Race Conditions auf einem Spiel zu vermeiden, das ich entwickle. Der Spielserver hat ein einfaches Ziel: zwei Spieler zu paaren.Django 1.6 Transaktionen, um Race Conditions zu vermeiden

Mein aktueller Ansatz ist:

  1. Benutzer will
  2. prüft der Server spielen, wenn es jemand wartet anderes zu spielen.
    1. Wenn dort nicht ist, wird ein GameConnection-Objekt erstellt (das eine eindeutige ID hat - uuid4).
    2. Wenn ist, erhält es die GameConnection-Kennung und löscht die GameConnection.

Dies ist der Code:

# 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 

Offensichtlich gibt es eine Race-Bedingung oben auf dem Code. Da dieser Code nicht atomar ist, kann es passieren, dass:

  • A überprüft für Spiele-Verbindungen. Findet keine.
  • A erstellt eine Spielverbindung.
  • B prüft auf Spielverbindungen. Findet einen (A).
  • C sucht nach Spielverbindungen. Findet einen (A).
  • B erhält die Verbindungskennung A und startet ein Spiel.
  • C erhält die Verbindungskennung von A und startet ein Spiel.

Ich habe versucht, sie aber diese ganze Block unter with transaction.atomic(): oder den @transaction.atomic Dekorateur zu verwenden. Trotzdem kann ich die Race Condition reproduzieren.

Ich bin sicher, da ist etwas über die Transaktionsdynamik, die ich hier vermisse. Kann jemand Licht werfen?

+0

Ich bin kein Experte, aber nach meinem Verständnis gelten die atomaren Transaktionen für Schreibvorgänge. In Ihrem Fall möchten Sie möglicherweise eine Art von Mutex-Sperren verwenden, um sicherzustellen, dass jeweils nur eine Paarung stattfindet. Dies ist vergleichbar mit @synchronized in Java. Allerdings nicht leistungsfähig. –

Antwort

2

@Sai ist auf dem richtigen Weg ... der Schlüssel ist, dass die Sperre/Mutex nicht vor einem Schreiben (oder Löschen) auftreten wird. Wie codiert, gibt es immer eine Zeit zwischen "discovery" (lesen) der ausstehenden Verbindung und "claim" (schreiben/sperren) der ausstehenden Verbindung, ohne dass man weiß, dass eine Verbindung gerade beansprucht wird.

Wenn Sie PostgreSQL verwenden (ziemlich sicher, dass MySQL unterstützt es auch), können Sie die Sperre erzwingen mit „wählen Sie für die Aktualisierung“, die eine weitere Anforderung ab, die gleiche Zeile verhindern, bis die Transaktion abgeschlossen ist:

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

Schlussbemerkung - Betrachten Sie etwas anderes als all(), um explizit zu sein, welches Spiel möglicherweise abgeholt wird (zB Bestellung durch einen "erstellten" Zeitstempel oder etwas). Ich hoffe, das hilft.