Skip to content

Commit

Permalink
Fixes #18 to close one of the oldest 10 games (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
dtest authored Oct 14, 2022
1 parent d54d183 commit 26ca771
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 45 deletions.
35 changes: 15 additions & 20 deletions generators/match_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,32 @@ class GameMatch(HttpUser):
leveraging the matchmaking-service
"""

open_games = []

@task(2)
def create_game(self):
"""Task to create a new game"""

headers = {"Content-Type": "application/json"}

# Create the game, then store the response in memory of list of open games.
with self.client.post("/games/create", headers=headers, catch_response=True) as response:
try:
game_uuid = response.json()
self.open_games.append({"gameUUID": game_uuid})
except json.JSONDecodeError as exc:
raise RescheduleTask() from exc
except KeyError:
response.failure("Response did not contain expected key 'gameUUID'")
self.client.post("/games/create", headers=headers)

@task(1)
def close_game(self):
"""Task to close a previously opened game"""
# No open games are in memory, reschedule task to run again later.
if len(self.open_games) == 0:
raise RescheduleTask()

headers = {"Content-Type": "application/json"}

# Close the first open game in our list, removing it to avoid contention
# from concurrent requests
game = self.open_games[0]
del self.open_games[0]
# Get an open game to be closed
with self.client.get("/games/open", headers=headers, catch_response=True) as response:
try:
game_uuid = response.json()["gameUUID"]

# Reschedule task when game_uuid is empty
if game_uuid == "":
raise RescheduleTask()

data = {"gameUUID": game["gameUUID"]}
self.client.put("/games/close", data=json.dumps(data), headers=headers)
data = {"gameUUID": game_uuid}
self.client.put("/games/close", data=json.dumps(data), headers=headers)
except json.JSONDecodeError:
response.failure("Response could not be decoded as JSON")
except KeyError:
response.failure("Response did not contain expected key 'gameUUID'")
44 changes: 19 additions & 25 deletions src/golang/matchmaking-service/models/games.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,8 @@ func GetOpenGame(ctx context.Context, client spanner.Client) (Game, error) {
var g Game

// Get an open game
// Initial inefficient query to get a random game that does not query from an index
query := fmt.Sprintf("SELECT gameUUID FROM (SELECT gameUUID FROM games WHERE finished IS NULL) TABLESAMPLE RESERVOIR (%d ROWS)", 1)

// TODO: A potential query to get the oldest open game, combined with an index on 'finished' is much faster.
// However, there are contention issues with concurrent queries due to this not closing the game in a
// single read-write transaction.
// query := "SELECT gameUUID FROM games WHERE finished IS NULL ORDER BY created DESC LIMIT 1"
// Retrieve a random unfinished from 10 of the oldest games to reduce concurrency contention when closing the same game
query := fmt.Sprintf("SELECT gameUUID FROM (SELECT gameUUID FROM games WHERE finished IS NULL ORDER BY created DESC LIMIT 10) TABLESAMPLE RESERVOIR (%d ROWS)", 1)

stmt := spanner.Statement{SQL: query}

Expand Down Expand Up @@ -182,8 +177,7 @@ func (g Game) updateGamePlayers(ctx context.Context, players []Player, txn *span
// Update player
// If player's current game isn't the same as this game, that's an error
if p.Current_game != g.GameUUID {
errorMsg := fmt.Sprintf("Player '%s' doesn't belong to game '%s'.", p.PlayerUUID, g.GameUUID)
return errors.New(errorMsg)
return fmt.Errorf("player '%s' doesn't belong to game '%s'", p.PlayerUUID, g.GameUUID)
}

cols := []string{"playerUUID", "current_game", "stats"}
Expand Down Expand Up @@ -271,22 +265,6 @@ func (g *Game) CloseGame(ctx context.Context, client spanner.Client) error {
// Close game
_, err := client.ReadWriteTransaction(ctx,
func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
// Get game players
playerUUIDs, players, err := g.getGamePlayers(ctx, txn)

if err != nil {
return err
}

// Might be an issue if there are no players!
if len(playerUUIDs) == 0 {
errorMsg := fmt.Sprintf("No players found for game '%s'", g.GameUUID)
return errors.New(errorMsg)
}

// Get random winner
g.Winner = determineWinner(playerUUIDs)

// Validate game finished time is null
row, err := txn.ReadRow(ctx, "games", spanner.Key{g.GameUUID}, []string{"finished"})
if err != nil {
Expand All @@ -303,6 +281,22 @@ func (g *Game) CloseGame(ctx context.Context, client spanner.Client) error {
return errors.New(errorMsg)
}

// Get game players
playerUUIDs, players, err := g.getGamePlayers(ctx, txn)

if err != nil {
return err
}

// Might be an issue if there are no players!
if len(playerUUIDs) == 0 {
errorMsg := fmt.Sprintf("No players found for game '%s'", g.GameUUID)
return errors.New(errorMsg)
}

// Get random winner
g.Winner = determineWinner(playerUUIDs)

cols := []string{"gameUUID", "finished", "winner"}
err = txn.BufferWrite([]*spanner.Mutation{
spanner.Update("games", cols, []interface{}{g.GameUUID, time.Now(), g.Winner}),
Expand Down

0 comments on commit 26ca771

Please sign in to comment.