Skip to content

Commit

Permalink
Clean empty, day old, lobbies on a 30min timer. (#63)
Browse files Browse the repository at this point in the history
Currently all lobbies stay in the database even if they are empty. This
change introduces a simple timer that deletes day old empty lobbies
every 30 minutes.

This is needed for better lobby listing (filters, private etc) in the
future.

Also snuck in the disabling of websocket compression, see
coder/websocket#218 (comment)
  • Loading branch information
koenbollen authored Oct 11, 2023
1 parent 01d2791 commit aac100f
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 21 deletions.
32 changes: 22 additions & 10 deletions internal/signaling/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"net/http"
"strings"
"sync"
"time"

Expand All @@ -19,12 +18,32 @@ import (

const MaxConnectionTime = 1 * time.Hour

const LobbyCleanInterval = 30 * time.Minute
const LobbyCleanThreshold = 24 * time.Hour

func Handler(ctx context.Context, store stores.Store, cloudflare *cloudflare.CredentialsClient) (*sync.WaitGroup, http.HandlerFunc) {
manager := &TimeoutManager{
Store: store,
}
go manager.Run(ctx)

go func() {
logger := logging.GetLogger(ctx)
ticker := time.NewTicker(LobbyCleanInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
logger.Info("cleaning empty lobbies")
if err := store.CleanEmptyLobbies(ctx, time.Now().Add(-LobbyCleanThreshold)); err != nil {
logger.Error("failed to clean empty lobbies", zap.Error(err))
}
case <-ctx.Done():
return
}
}
}()

wg := &sync.WaitGroup{}
return wg, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
Expand All @@ -34,17 +53,10 @@ func Handler(ctx context.Context, store stores.Store, cloudflare *cloudflare.Cre
ctx, cancel := context.WithTimeout(ctx, MaxConnectionTime)
defer cancel()

userAgentLower := strings.ToLower(r.Header.Get("User-Agent"))
isSafari := strings.Contains(userAgentLower, "safari") && !strings.Contains(userAgentLower, "chrome") && !strings.Contains(userAgentLower, "android")
acceptOptions := &websocket.AcceptOptions{
// Allow any origin/game to connect.
InsecureSkipVerify: true,
}

if isSafari {
acceptOptions.CompressionMode = websocket.CompressionDisabled
InsecureSkipVerify: true, // Allow any origin/game to connect.
CompressionMode: websocket.CompressionDisabled,
}

conn, err := websocket.Accept(w, r, acceptOptions)
if err != nil {
util.ErrorAndAbort(w, r, http.StatusBadRequest, "", err)
Expand Down
41 changes: 30 additions & 11 deletions internal/signaling/stores/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,12 @@ func (s *PostgresStore) CreateLobby(ctx context.Context, game, lobbyCode, peerID
logger.Warn("peer id too long", zap.String("peerID", peerID))
return ErrInvalidPeerID
}
now := util.Now(ctx)
res, err := s.DB.Exec(ctx, `
INSERT INTO lobbies (code, game, public)
VALUES ($1, $2, true)
INSERT INTO lobbies (code, game, public, created_at, updated_at)
VALUES ($1, $2, true, $3, $3)
ON CONFLICT DO NOTHING
`, lobbyCode, game)
`, lobbyCode, game, now)
if err != nil {
return err
}
Expand All @@ -171,6 +172,9 @@ func (s *PostgresStore) JoinLobby(ctx context.Context, game, lobbyCode, peerID s
logger.Warn("peer id too long", zap.String("peerID", peerID))
return nil, ErrInvalidPeerID
}

now := util.Now(ctx)

tx, err := s.DB.Begin(ctx)
if err != nil {
return nil, err
Expand Down Expand Up @@ -200,10 +204,12 @@ func (s *PostgresStore) JoinLobby(ctx context.Context, game, lobbyCode, peerID s

_, err = tx.Exec(ctx, `
UPDATE lobbies
SET peers = array_append(peers, $1)
WHERE code = $2
AND game = $3
`, peerID, lobbyCode, game)
SET
peers = array_append(peers, $1),
updated_at = $2
WHERE code = $3
AND game = $4
`, peerID, now, lobbyCode, game)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -232,14 +238,18 @@ func (s *PostgresStore) IsPeerInLobby(ctx context.Context, game, lobbyCode, peer
}

func (s *PostgresStore) LeaveLobby(ctx context.Context, game, lobbyCode, peerID string) ([]string, error) {
now := util.Now(ctx)

var peerlist []string
err := s.DB.QueryRow(ctx, `
UPDATE lobbies
SET peers = array_remove(peers, $1)
WHERE code = $2
AND game = $3
SET
peers = array_remove(peers, $1),
updated_at = $2
WHERE code = $3
AND game = $4
RETURNING peers
`, peerID, lobbyCode, game).Scan(&peerlist)
`, peerID, now, lobbyCode, game).Scan(&peerlist)
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
return nil, err
}
Expand Down Expand Up @@ -392,3 +402,12 @@ func (s *PostgresStore) ClaimNextTimedOutPeer(ctx context.Context, threshold tim

return true, tx.Commit(ctx)
}

func (s *PostgresStore) CleanEmptyLobbies(ctx context.Context, olderThan time.Time) error {
_, err := s.DB.Exec(ctx, `
DELETE FROM lobbies
WHERE updated_at < $1
AND peers = '{}'
`, olderThan)
return err
}
2 changes: 2 additions & 0 deletions internal/signaling/stores/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type Store interface {
TimeoutPeer(ctx context.Context, peerID, secret, gameID string, lobbies []string) error
ReconnectPeer(ctx context.Context, peerID, secret, gameID string) (bool, []string, error)
ClaimNextTimedOutPeer(ctx context.Context, threshold time.Duration, callback func(peerID, gameID string, lobbies []string) error) (bool, error)

CleanEmptyLobbies(ctx context.Context, olderThan time.Time) error
}

type Lobby struct {
Expand Down

0 comments on commit aac100f

Please sign in to comment.