Skip to content

Commit 8981808

Browse files
authored
Merge pull request #66 from CS3219-AY2425S1/matching-logging-and-bug-fixing
Matching logging and bug fixing
2 parents 8fbc6b6 + c884ca3 commit 8981808

File tree

12 files changed

+54
-626
lines changed

12 files changed

+54
-626
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ This project follows a microservices architecture with the following services:
99
4. **Matching Service** - Port `3003`
1010
5. **MongoDB** - Port `27017` (Database)
1111
6. **Nginx API Gateway** - Port `80`
12+
7. **Redis** - Port `6379`
13+
14+
### Setting up the Project
15+
Copy and paste the .env.example files in each service. Rename them as .env files.
16+
Files to do this in:
17+
1. ./
18+
2. /frontend
19+
3. /backend/user-service
20+
4. /backend/question-service
21+
5. /backend/matching-service
22+
Consult the readme files in the service if there are further configurations needed.
1223

1324
### Running the Project
1425

@@ -23,6 +34,7 @@ Once the containers are up:
2334
- Matching Service: [http://localhost:3003](http://localhost:3003)
2435
- MongoDB: [http://localhost:27017](http://localhost:27017)
2536
- Nginx API Gateway: [http://localhost:80](http://localhost:80)
37+
- Redis: [http://localhost:6379](http://localhost:6379)
2638

2739
### MongoDB Configuration
2840

backend/matching-service/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ REDIS_PORT=6379
1111
# Matching time periods
1212
MATCHING_INTERVAL=1000
1313
RELAXATION_INTERVAL=3000
14-
MATCH_TIMEOUT=15000
14+
MATCH_TIMEOUT=30000
1515
CLEANUP_INTERVAL=75000
1616

1717
# Copy root .env

backend/matching-service/README.md

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,34 @@
1-
Hey, this is Nephelite again.
1+
Matching service backend.
22

3-
This service can be accessed both via REST API routes, and purely through websockets (via socket.Io). If you use REST, you must also send the client's socketId, so you need to retrieve it on the frontend first. If you are communicating purely via websockets, then you just need to send { username, email, category, difficulty } along with the event name of `startMatching`.
3+
To configure the .env files, create a copy of .env.example and rename it .env.
4+
If you edited the .env file on the root level, copy and paste it in.
45

5-
To test the local implementation of this service without frontend, follow the steps below:
6+
.env variables:
7+
# Database Connection String Details
8+
DATABASE_NAME=peerprepMatchingServiceDB
9+
Should be the same as the root .env file.
610

7-
1. Run `npm install` at this level (root of matching-service).
8-
2. Configure the .env file. Edit the `DB_REQUIRE_AUTH` field in .env to be false for local.
9-
3. Successful match events will be recorded in MongoDB, under the Database `peerprepMatchingServiceDB` and `matchingevents` collection.
10-
4. Open 2 terminals: One to run the service, the other to run the test script.
11-
5. On either terminal: Open Docker, then run `docker pull redis:latest`, then `docker run --name redis-local -p 6379:6379 -d redis`.
12-
6. First terminal: Run `npm run dev`. Wait for the messages that the server is running and that it has successfully connected to MongoDB.
13-
7. Second terminal: Run `node testClientREST` or `node testClientEvent` and watch the tests go. The former uses the API calls, the latter uses exclusively the socket.io communication.
11+
# Port to run service on
12+
PORT=3003
13+
Port of the service.
1414

15-
To add test cases, add them at the bottom of the testClient.js file.
15+
# Redis configuration
16+
REDIS_HOST=localhost
17+
REDIS_PORT=6379
18+
Redis uri configurations. Note: The port matters for all forms of deployment, but
19+
editing REDIS_HOST here only affects the local deployment. To affect the name of
20+
the redis uri, edit the docker-compose.yml file instead. This environment variable is
21+
overriden there.
1622

17-
The testClient.js file also allows you to define delay as you wish, so you can use that to test your race condition prevention delay measures, though I also tried to ensure the implementation of matching here avoids race conditions as much as possible.
23+
# Matching time periods (In milliseconds)
24+
MATCHING_INTERVAL=1000
25+
The matching worker checks for matches for matches every ^ milliseconds.
26+
RELAXATION_INTERVAL=3000
27+
The matching worker relaxes requirements from users for matching every ^ milliseconds.
28+
The first time ^ milliseconds passes, the users will be matched with only category considered.
29+
The second time ^ milliseconds passes, the users will be matched with only difficulty considered.
30+
At least one of category or difficulty must match.
31+
MATCH_TIMEOUT=30000
32+
Users will be considered as having timed out if the ^ milliseconds have passed.
33+
CLEANUP_INTERVAL=75000
34+
Stale users will be checked and cleansed every ^ milliseconds by the staleUserCleaner.

backend/matching-service/constants/queueTypes.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

backend/matching-service/controllers/matchingController.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { SOCKET_EVENTS } from "../constants/socketEventNames";
44
import { MATCHING_STATUS } from "../constants/matchingStatus";
55
import redisClient, { createRedisClient, logAllQueues } from "../utils/redisClient";
66
import { decodeToken } from "../utils/tokenUtils";
7-
import Room from "../models/RoomSchema";
87

98
import { startStaleUserCleanup } from '../workers/staleUserCleaner';
109
import { isValidSocketId } from "../utils/helpers";
@@ -97,7 +96,6 @@ export function setupSocketListeners() {
9796

9897
const token = socket.handshake.auth?.token;
9998

100-
// Use the decodeToken utility to decode the token
10199
const decodedToken = decodeToken(token);
102100
if (!decodedToken) {
103101
socket.emit('error', { message: 'Invalid or missing token' });
@@ -107,7 +105,6 @@ export function setupSocketListeners() {
107105
const userId = decodedToken.id;
108106
console.log(`User connected: ${socket.id}, User ID: ${userId}`);
109107

110-
// Save user ID with socket ID in Redis only on connect
111108
const userData = {
112109
userId,
113110
socketId: socket.id,
@@ -116,7 +113,6 @@ export function setupSocketListeners() {
116113
difficulty: difficulty,
117114
};
118115
redisClient.hSet(userKey, userData);
119-
// Log the saved user data
120116
console.log('User data saved:', { userKey, userData });
121117

122118
await redisClient.zAdd('matching_queue', {
@@ -143,7 +139,6 @@ export function setupSocketListeners() {
143139
console.log(`User disconnected: ${socketId}`);
144140
const userKey = `user:${socketId}`;
145141

146-
// Remove user from queue and delete data
147142
await redisClient.zRem('matching_queue', userKey);
148143
await redisClient.del(userKey);
149144
});
@@ -157,9 +152,8 @@ export async function setupSubscriber() {
157152

158153
const streamKey = 'match_events';
159154
const consumerGroup = 'match_consumers';
160-
const consumerName = `consumer_${process.pid}`; // Unique consumer name
155+
const consumerName = `consumer_${process.pid}`;
161156

162-
// Create the consumer group if it doesn't exist
163157
try {
164158
await redisClientInstance.xGroupCreate(streamKey, consumerGroup, '0', { MKSTREAM: true });
165159
console.log(`Consumer group '${consumerGroup}' created.`);
@@ -171,10 +165,8 @@ export async function setupSubscriber() {
171165
}
172166
}
173167

174-
// Start processing messages
175168
processMatchEvents(redisClientInstance, streamKey, consumerGroup, consumerName);
176169

177-
// Start the periodic cleanup for stale users
178170
startStaleUserCleanup();
179171
}
180172

@@ -210,17 +202,14 @@ async function processMatchEvents(
210202
const id = message.id;
211203
const fields = message.message;
212204

213-
// Parse the match message
214205
const user1 = JSON.parse(fields.user1);
215206
const user2 = JSON.parse(fields.user2);
216207
const roomId = fields.roomId;
217208
const matchId = fields.matchId;
218209

219-
// Log the message being processed
220210
console.log(`Processing MatchEvent ID: ${matchId} from Stream ID: ${id}`);
221211
console.log("Match Message Details:", { user1, user2, roomId, matchId });
222212

223-
// Handle the match event
224213
await handleMatchEvent(user1, user2, roomId, matchId);
225214
await redisClient.xAck(streamKey, consumerGroup, id);
226215
await redisClient.xDel(streamKey, id);
@@ -234,12 +223,10 @@ async function processMatchEvents(
234223
}
235224
}
236225

237-
// Handle match event function
238226
async function handleMatchEvent(user1: any, user2: any, roomId: string, matchId: string) {
239227
try {
240228
const io = getSocket();
241229

242-
// Log the state of the message queue before processing the match event
243230
console.log("Before processing messages, message queue state:");
244231
const messagesBefore = await redisClient.xRange('match_events', '-', '+', { COUNT: 10 });
245232
console.log(messagesBefore);
@@ -248,11 +235,9 @@ async function handleMatchEvent(user1: any, user2: any, roomId: string, matchId:
248235
const socket2 = io.sockets.sockets.get(user2.socketId);
249236

250237
if (socket1 && socket2) {
251-
// Both users join the newly created room
252238
socket1.join(roomId);
253239
socket2.join(roomId);
254240

255-
// Emit match found event to both users with match details, including roomId and userId
256241
socket1.emit(SOCKET_EVENTS.MATCH_FOUND, {
257242
message: `You have been matched with User ID: ${user2.userId}`,
258243
category: user1.category || user2.category || 'Any',

0 commit comments

Comments
 (0)