3

Tastypieのドキュメントには、バンドルによってTastypieのスレッドセーフが維持されると記載されていますが、その方法と条件については説明されていません。私はコードを調べましたが、頭を包み込むほどの経験はありません。

ラウンドオブジェクト(プレイのラウンドごと)とラウンドごとに複数の状態(そのラウンドの各プレーヤーの情報)を持つゲームのプロトタイプを作成しています。各プレーヤーは、ラウンドの単語フレーズへの回答で自分の状態を更新します。次のラウンドがまだ存在しない場合は、怠惰に次のラウンドを作成するメカニズムが必要です。私は現在、プレイヤーが状態を更新したときにそのラウンドの作成をトリガーします。

複数のプレーヤーが同時に状態を更新した場合(を参照StateResource.obj_update()) 、次のラウンドを作成する試みは衝突する可能性がありますか?obj_updateこれは、ある呼び出しが次のラウンドが存在するかどうかを確認し、別の呼び出しが次のラウンドの作成を完了する前に次のラウンドを作成しようとした場合に発生する可能性があると考えていますobj_update。ある種のミューテックスでこれを解決しますが、それが必要かどうかはわかりません。これを解決するためのTastypie-wayがあるかどうか疑問に思います。

私のコードは次のとおりです。

#models.py
class Round(models.Model):
    game_uid = models.CharField(max_length=75)
    word = models.CharField(max_length=75)
    players = models.ManyToManyField(User)
    next_round = models.OneToOneField('self',null=True,blank=True)

class PlayerRoundState(models.Model):
    player = models.ForeignKey(User)
    round = models.ForeignKey(Round)
    answer = models.CharField(max_length=75)

#api.py
class RoundResource(ModelResource):
    players = fields.ManyToManyField(UserResource, attribute='players',full=False)
    states = fields.ManyToManyField('wordgame.api.StateResource',
                                attribute='playerroundstate_set',
                                full=True)
    . . .
    def obj_create(self, bundle, request=None, **kwargs):
        bundle = super(RoundResource, self).obj_create(bundle, request,**kwargs)
        bundle.obj.word = choice(words) #Gets a random word from a list
        bundle.obj.round_number = 1
        bundle.obj.game_uid = bundle.obj.calc_guid() #Creates a unique ID for the game
        bundle.obj.save()
        return bundle

class StateResource(ModelResource):
    player = fields.ForeignKey(UserResource, 'player',full=False)
    round = fields.ForeignKey(RoundResource, 'round')
    . . . 
    def obj_update(self, bundle, request=None, skip_errors=False, **kwargs):
        bundle = super(StateResource, self).obj_update(bundle, request,
                                                   skip_errors, **kwargs)
        if bundle.obj.round.next_round is None:
            new_round = Round()
            new_round.word = choice(words)
            new_round.round_number = bundle.obj.round.round_number + 1
            new_round.game_uid = bundle.obj.round.game_uid
            new_round.save()
            for p in bundle.obj.round.players.all():
                new_round.players.add(p)
            new_round.save()
            bundle.obj.round.next_round = new_round
            bundle.obj.round.save()

        return bundle
4

1 に答える 1

0

これはTastypieとはあまり関係がないと思います。

あなたが説明している問題は、むしろORMとデータベースに関連しています。問題は、両方のリクエストが特定の状況下で新しいRound()ものを作成する可能性があり(gunicornとgeventの場合のように、並行して処理され、いつでも切り替えることができる場合)、一方が古くなる可能性があることです。

次の状況を考慮してください。

最初のリクエストが到着し、現在のラウンドを取得して、「次の」ラウンドがないことを「確認」します。したがって、次のように実行されます。

new_round = Round()
new_round.word = choice(words)
new_round.round_number = bundle.obj.round.round_number + 1
new_round.game_uid = bundle.obj.round.game_uid
new_round.save()

その間に、2番目の要求が来て、(セットアップで可能であると仮定して)処理がその2番目の要求に切り替わります。また、現在のラウンドを取得し、次のラウンドがないことを「認識」するため、次のラウンドも作成します(同じ論理ラウンドの2番目のオブジェクト)。

次に、処理は、以下を実行する最初の要求に戻ります。

for p in bundle.obj.round.players.all():
    new_round.players.add(p)
new_round.save()
bundle.obj.round.next_round = new_round
bundle.obj.round.save()

だから今、次のラウンドが「あります」。最初のリクエストが処理され、すべてが正常に見えます。ただし、2番目の要求はまだ完了しておらず、まったく同じ操作を実行して、現在のラウンドオブジェクトを上書きします。

その結果、1つの古いインスタンス(Round最初のリクエストによって作成された)があり、プレーヤーの最初のグループがRound2番目のグループとは異なるものを使用します。

これにより、データベースの状態に一貫性がなくなります。したがって、このような場合、リソース更新メソッドはスレッドセーフではありません。

これに対する1つの解決策select_for_updateは、データベースから現在のラウンドを取得するために使用することです。DjangoDocsを参照してください。これを使用した場合、2番目以降のリクエストは、最初のリクエストで現在のラウンドを変更するまで待機してから、データベースから取得します。その結果、彼らはすでに次のラウンドを「見て」、それを作成しようとはしませんでした。もちろん、更新全体が1つのトランザクションを構成することを確認する必要があります。

それを「使用」する方法は、次の代わりに、リソースobj_get()のメソッドをオーバーライドすることです。StateResource

base_object_list = self.get_object_list(request).filter(**kwargs)

使用(テストされていません):

base_object_list = self.get_object_list(request).select_for_update().filter(**kwargs)

もちろん、これが唯一の解決策ではありませんが、他の解決策はおそらくアプリケーションの再設計を伴うので、これはそれほど複雑ではないかもしれません。

于 2012-09-05T02:21:06.713 に答える