Skip to content

Commit

Permalink
Merge pull request #41 from CS3219-AY2425S1/titus/matching-service-cl…
Browse files Browse the repository at this point in the history
…eanups

refactor: clean up files in matching-service
  • Loading branch information
bensohh authored Oct 19, 2024
2 parents fc0fdac + d1908ef commit 4e27b86
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 247 deletions.
2 changes: 1 addition & 1 deletion apps/matching-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ go mod tidy
- `MATCH_TIMEOUT`: The time in seconds to wait for a match before timing out.
- `REDIS_URL`: The URL for the Redis server. Default is `localhost:6379`.

4. Start a local redis server:
4. Start a local Redis server:

```bash
docker run -d -p 6379:6379 redis
Expand Down
93 changes: 32 additions & 61 deletions apps/matching-service/handlers/websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,44 +123,42 @@ func createTimeoutContext() (context.Context, context.CancelFunc, error) {
return ctx, cancel, nil
}

// Cleans up the data associated with the user before ending the websocket connection.
// If user is already removed, then nothing happens.
func cleanUpUser(username string) {
// Cleanup Redis
processes.CleanUpUser(processes.GetRedisClient(), username, context.Background())

// Removes the match context and active
if cancelFunc, exists := matchContexts[username]; exists {
cancelFunc()
delete(matchContexts, username)
}
if _, exists := activeConnections[username]; exists {
delete(activeConnections, username)
}
if _, exists := matchFoundChannels[username]; exists {
delete(matchFoundChannels, username)
}
}

// waitForResult waits for a match result, timeout, or cancellation.
func waitForResult(ws *websocket.Conn, ctx, timeoutCtx, matchCtx context.Context, matchFoundChan chan models.MatchFound, username string) {
select {
case <-ctx.Done():
log.Println("Matching cancelled")
// Cleanup Redis
processes.CleanUpUser(processes.GetRedisClient(), username, context.Background())
// Remove the match context and active
if _, exists := matchContexts[username]; exists {
delete(matchContexts, username)
}
if _, exists := activeConnections[username]; exists {
delete(activeConnections, username)
}
if _, exists := matchFoundChannels[username]; exists {
delete(matchFoundChannels, username)
}

cleanUpUser(username)
return
case <-timeoutCtx.Done():
log.Println("Connection timed out")
// Cleanup Redis
processes.CleanUpUser(processes.GetRedisClient(), username, context.Background())
// Remove the match context and active
if _, exists := matchContexts[username]; exists {
delete(matchContexts, username)
}
if _, exists := activeConnections[username]; exists {
delete(activeConnections, username)
}
if _, exists := matchFoundChannels[username]; exists {
delete(matchFoundChannels, username)
}

sendTimeoutResponse(ws)
cleanUpUser(username)
return
case <-matchCtx.Done():
log.Println("Match found for user: " + username)

// NOTE: user is already cleaned-up in the other process,
// so there is no need to clean up again.
return
case result, ok := <-matchFoundChan:
if !ok {
Expand All @@ -169,8 +167,11 @@ func waitForResult(ws *websocket.Conn, ctx, timeoutCtx, matchCtx context.Context
return
}
log.Println("Match found for user: " + username)
// Notify the users about the match
notifyMatch(result.User, result.MatchedUser, result)
// Notify the user about the match
notifyMatches(result.User, result)

// NOTE: user and other user are already cleaned up in a separate matching algorithm process
// so no clean up is required here.
return
}
}
Expand All @@ -186,45 +187,15 @@ func sendTimeoutResponse(ws *websocket.Conn) {
}
}

func notifyMatch(username, matchedUsername string, result models.MatchFound) {
// Notify matches
func notifyMatches(username string, result models.MatchFound) {
mu.Lock()
defer mu.Unlock()

// Send message to the first user
// Send message to matched user
if userConn, userExists := activeConnections[username]; userExists {
if err := userConn.WriteJSON(result); err != nil {
log.Printf("Error sending message to user %s: %v\n", username, err)
}
}

// Send message to the matched user
if matchedUserConn, matchedUserExists := activeConnections[matchedUsername]; matchedUserExists {
result.User, result.MatchedUser = result.MatchedUser, result.User // Swap User and MatchedUser values
if err := matchedUserConn.WriteJSON(result); err != nil {
log.Printf("Error sending message to user %s: %v\n", username, err)
}
}

// Remove the match context for both users and cancel for matched user
if cancelFunc, exists := matchContexts[username]; exists {
cancelFunc()
delete(matchContexts, username)
}

if cancelFunc2, exists := matchContexts[matchedUsername]; exists {
cancelFunc2()
delete(matchContexts, matchedUsername)
}

// Remove the match channels
if _, exists := matchFoundChannels[username]; exists {
delete(matchFoundChannels, username)
}
if _, exists := matchFoundChannels[matchedUsername]; exists {
delete(matchFoundChannels, matchedUsername)
}

// Remove users from the activeConnections map
delete(activeConnections, username)
delete(activeConnections, matchedUsername)
}
30 changes: 6 additions & 24 deletions apps/matching-service/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"context"
"fmt"
"log"
"matching-service/handlers"
Expand All @@ -10,7 +9,6 @@ import (
"os"

"github.com/joho/godotenv"
"github.com/redis/go-redis/v9"
)

func main() {
Expand All @@ -19,33 +17,17 @@ func main() {
if err != nil {
log.Fatalf("err loading: %v", err)
}
port := os.Getenv("PORT")

// Retrieve redis url env variable and setup the redis client
redisAddr := os.Getenv("REDIS_URL")
client := redis.NewClient(&redis.Options{
Addr: redisAddr,
Password: "", // no password set
DB: 0, // use default DB
})

// Ping the redis server
_, err = client.Ping(context.Background()).Result()
if err != nil {
log.Fatalf("Could not connect to Redis: %v", err)
} else {
log.Println("Connected to Redis at the following address: " + redisAddr)
}

// Set redis client
processes.SetRedisClient(client)

// Setup redis client
processes.SetupRedisClient()

// Run a goroutine that matches users

// Routes
http.HandleFunc("/match", handlers.HandleWebSocketConnections)

// Start the server
port := os.Getenv("PORT")
log.Println(fmt.Sprintf("Server starting on :%s", port))
err = http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
if err != nil {
Expand Down
67 changes: 67 additions & 0 deletions apps/matching-service/models/complexity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package models

import (
"log"
"strings"
)

// Get the highest common difficulty (aka complexity) between two users,
// If no common difficulty found, choose the min of the 2 arrays.
func GetCommonDifficulty(userArr []string, matchedUserArr []string) string {
commonDifficulties := make([]int, 3)
for i := range commonDifficulties {
commonDifficulties[i] = 0
}

for _, difficulty := range userArr {
formattedDifficulty := strings.ToLower(difficulty)
switch formattedDifficulty {
case "easy":
commonDifficulties[0]++
case "medium":
commonDifficulties[1]++
case "hard":
commonDifficulties[2]++
default:
log.Println("Unknown difficulty specified: " + difficulty)
}
}

for _, difficulty := range matchedUserArr {
formattedDifficulty := strings.ToLower(difficulty)
switch formattedDifficulty {
case "easy":
commonDifficulties[0]++
case "medium":
commonDifficulties[1]++
case "hard":
commonDifficulties[2]++
default:
log.Println("Unknown difficulty specified: " + difficulty)
}
}

lowest := "Hard"
for i := 2; i >= 0; i-- {
if commonDifficulties[i] == 2 {
switch i {
case 0:
return "Easy"
case 1:
return "Medium"
case 2:
return "Hard"
}
} else if commonDifficulties[i] > 0 {
switch i {
case 0:
lowest = "Easy"
case 1:
lowest = "Medium"
case 2:
lowest = "Hard"
}
}
}
return lowest
}
17 changes: 17 additions & 0 deletions apps/matching-service/processes/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package processes

import (
"context"
"sync"

"github.com/redis/go-redis/v9"
)

const matchmakingQueueRedisKey = "matchmaking_queue"

var (
redisClient *redis.Client
matchingRoutineMutex sync.Mutex // Mutex to ensure only one matchmaking goroutine is running
redisAccessMutex sync.Mutex // Mutex for Redis access for concurrency safety
ctx = context.Background()
)
Loading

0 comments on commit 4e27b86

Please sign in to comment.