Skip to content

Commit 9834600

Browse files
committed
feat: handle duplicate connection
1 parent a483202 commit 9834600

File tree

3 files changed

+57
-44
lines changed

3 files changed

+57
-44
lines changed

apps/matching-service/handlers/websocket.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package handlers
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"log"
78
"matching-service/databases"
89
"matching-service/models"
@@ -50,8 +51,6 @@ func HandleWebSocketConnections(w http.ResponseWriter, r *http.Request) {
5051
return
5152
}
5253

53-
// TODO: Checks if user is already an existing websocket connection
54-
5554
// Subscribes to a channel that returns a message if a match is found
5655
matchFoundPubsub := rdb.Subscribe(ctx, matchRequest.Username)
5756
defer matchFoundPubsub.Close()
@@ -60,6 +59,9 @@ func HandleWebSocketConnections(w http.ResponseWriter, r *http.Request) {
6059
userCtx, userCancel := context.WithCancel(ctx)
6160
defer userCancel() // Ensure cancel is called to release resources
6261

62+
// Create channel for handling errors
63+
errorChan := make(chan error)
64+
6365
// Create a context for matching timeout
6466
timeoutCtx, timeoutCancel, err := utils.CreateTimeoutContext()
6567
if err != nil {
@@ -70,10 +72,10 @@ func HandleWebSocketConnections(w http.ResponseWriter, r *http.Request) {
7072

7173
// Start goroutines for handling messages and performing matching.
7274
go processes.ReadMessages(ws, userCancel)
73-
go processes.PerformMatching(matchRequest, ctx) // Perform matching
75+
go processes.PerformMatching(rdb, matchRequest, ctx, errorChan)
7476

7577
// Wait for a match, timeout, or cancellation.
76-
waitForResult(ws, userCtx, timeoutCtx, matchFoundPubsub, matchRequest.Username)
78+
waitForResult(ws, userCtx, timeoutCtx, matchFoundPubsub, errorChan, matchRequest.Username)
7779
}
7880

7981
// readMatchRequest reads the initial match request from the WebSocket connection.
@@ -105,7 +107,7 @@ func cleanUpUser(username string) {
105107
}
106108

107109
// waitForResult waits for a match result, timeout, or cancellation.
108-
func waitForResult(ws *websocket.Conn, userCtx, timeoutCtx context.Context, matchFoundPubsub *redis.PubSub, username string) {
110+
func waitForResult(ws *websocket.Conn, userCtx, timeoutCtx context.Context, matchFoundPubsub *redis.PubSub, errorChan chan error, username string) {
109111
select {
110112
case <-userCtx.Done():
111113
log.Printf("Matching cancelled for port %v", utils.ExtractWebsocketPort(ws))
@@ -116,10 +118,18 @@ func waitForResult(ws *websocket.Conn, userCtx, timeoutCtx context.Context, matc
116118
sendTimeoutResponse(ws)
117119
cleanUpUser(username)
118120
return
121+
case err, ok := <-errorChan:
122+
if !ok {
123+
return
124+
}
125+
if errors.Is(err, models.ExistingUserError) {
126+
sendRejectionResponse(ws)
127+
} else {
128+
cleanUpUser(username)
129+
}
130+
return
119131
case msg, ok := <-matchFoundPubsub.Channel():
120132
if !ok {
121-
// Channel closed without a match, possibly due to context cancellation
122-
log.Println("Match found channel closed without finding a match")
123133
return
124134
}
125135
var result models.MatchFound
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package models
2+
3+
import "errors"
4+
5+
var ExistingUserError = errors.New("already has an existing user in matchmaking")

apps/matching-service/processes/performmatches.go

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package processes
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"log"
78
"matching-service/databases"
89
"matching-service/models"
@@ -13,68 +14,65 @@ import (
1314

1415
// Performs the matching algorithm at most once starting from the front of the queue of users,
1516
// until a match is found or no match and the user is enqueued to the queue.
16-
func PerformMatching(matchRequest models.MatchRequest, ctx context.Context) {
17-
redisClient := databases.GetRedisClient()
18-
19-
err := redisClient.Watch(ctx, func(tx *redis.Tx) error {
17+
func PerformMatching(rdb *redis.Client, matchRequest models.MatchRequest, ctx context.Context, errorChan chan error) {
18+
err := rdb.Watch(ctx, func(tx *redis.Tx) error {
2019
// Log queue before and after matchmaking
2120
databases.PrintMatchingQueue(tx, "Before Matchmaking", ctx)
2221
defer databases.PrintMatchingQueue(tx, "After Matchmaking", ctx)
2322

24-
// Iterate through the users in the queue from the front of the queue
23+
currentUsername := matchRequest.Username
2524
queuedUsernames, err := databases.GetAllQueuedUsers(tx, ctx)
2625
if err != nil {
2726
return err
2827
}
2928

30-
currentUsername := matchRequest.Username
31-
databases.AddUser(tx, matchRequest, ctx)
32-
29+
// Check that user is not part of the existing queue
3330
for _, username := range queuedUsernames {
34-
// Skip same user
3531
if username == currentUsername {
36-
// WARN: same user should not appear, since user is added after the queue users is accessed
37-
// so this means that the user has another active websocket connection
38-
continue
32+
return models.ExistingUserError
3933
}
34+
}
4035

41-
// Find a matching user if any
42-
matchFound, err := databases.FindMatchingUser(tx, currentUsername, ctx)
43-
if err != nil {
44-
log.Println("Error finding matching user:", err)
45-
return err
46-
}
36+
databases.AddUser(tx, matchRequest, ctx)
4737

48-
if matchFound != nil {
49-
matchedUsername := matchFound.MatchedUser
50-
matchedTopic := matchFound.Topic
51-
matchedDifficulty := matchFound.Difficulty
38+
// Find a matching user if any
39+
matchFound, err := databases.FindMatchingUser(tx, currentUsername, ctx)
40+
if err != nil {
41+
log.Println("Error finding matching user:", err)
42+
return err
43+
}
44+
45+
if matchFound != nil {
46+
matchedUsername := matchFound.MatchedUser
47+
matchedTopic := matchFound.Topic
48+
matchedDifficulty := matchFound.Difficulty
5249

53-
// Generate a random match ID
54-
matchId, err := utils.GenerateMatchID()
55-
if err != nil {
56-
log.Println("Unable to randomly generate matchID")
57-
}
50+
// Generate a random match ID
51+
matchId, err := utils.GenerateMatchID()
52+
if err != nil {
53+
log.Println("Unable to randomly generate matchID")
54+
}
5855

59-
// Log down which users got matched
60-
matchFound.MatchID = matchId
61-
log.Printf("Users %s and %s matched on the topic: %s with difficulty: %s", currentUsername, matchedUsername, matchedTopic, matchedDifficulty)
56+
// Log down which users got matched
57+
matchFound.MatchID = matchId
58+
log.Printf("Users %s and %s matched on the topic: %s with difficulty: %s", currentUsername, matchedUsername, matchedTopic, matchedDifficulty)
6259

63-
// Clean up redis for this match
64-
databases.CleanUpUser(tx, currentUsername, ctx)
65-
databases.CleanUpUser(tx, matchedUsername, ctx)
60+
// Clean up redis for this match
61+
databases.CleanUpUser(tx, currentUsername, ctx)
62+
databases.CleanUpUser(tx, matchedUsername, ctx)
6663

67-
publishMatch(tx, ctx, currentUsername, matchedUsername, matchFound)
68-
publishMatch(tx, ctx, matchedUsername, currentUsername, matchFound)
69-
}
64+
publishMatch(tx, ctx, currentUsername, matchedUsername, matchFound)
65+
publishMatch(tx, ctx, matchedUsername, currentUsername, matchFound)
7066
}
7167

7268
return nil
7369
})
7470
if err != nil {
7571
// Handle error (like retry logic could be added here)
7672
// return fmt.Errorf("transaction execution failed: %v", err)
77-
return
73+
if errors.Is(err, models.ExistingUserError) {
74+
errorChan <- err
75+
}
7876
}
7977
}
8078

0 commit comments

Comments
 (0)