Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: clean up files in matching-service #41

Merged
merged 2 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)

tituschewxj marked this conversation as resolved.
Show resolved Hide resolved
// 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