This commit is contained in:
MathieuSevignyLavallee 2024-11-28 15:09:22 -05:00
parent 71353669ca
commit ec15909d55
5 changed files with 104 additions and 70 deletions

View file

@ -9,60 +9,71 @@ export class RoomParticipant {
this.retryDelay = 1000; this.retryDelay = 1000;
} }
async connectToRoom(baseUrl, onConnectCallback) { async connectToRoom(baseUrl) {
let retries = 0; let retries = 0;
const maxRetries = 2;
const retryDelay = 2000;
const connect = () => { const cleanup = () => {
return new Promise((resolve, reject) => { if (this.socket) {
try { this.socket.removeAllListeners();
const socket = io(baseUrl, { this.socket.disconnect();
path: `/api/room/${this.roomName}/socket`, this.socket = null;
transports: ['websocket'], }
timeout: 5000, };
reconnection: true,
reconnectionAttempts: 3,
reconnectionDelay: 1000,
});
const connectionTimeout = setTimeout(() => { while (retries < maxRetries) {
socket.close(); try {
const socket = io(baseUrl, {
path: `/api/room/${this.roomName}/socket`,
transports: ['websocket'],
timeout: 8000,
reconnection: false,
forceNew: true
});
const result = await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
cleanup();
reject(new Error('Connection timeout')); reject(new Error('Connection timeout'));
}, 5000); }, 8000);
socket.on('connect', () => { socket.on('connect', () => {
clearTimeout(connectionTimeout); clearTimeout(timeout);
this.socket = socket; this.socket = socket;
if (onConnectCallback) { this.onConnected(); // Add this line
onConnectCallback();
}
resolve(socket); resolve(socket);
}); });
socket.on('connect_error', (error) => { socket.on('connect_error', (error) => {
clearTimeout(connectionTimeout); clearTimeout(timeout);
cleanup();
reject(new Error(`Connection error: ${error.message}`)); reject(new Error(`Connection error: ${error.message}`));
}); });
} catch (error) { socket.on('error', (error) => {
reject(error); clearTimeout(timeout);
} cleanup();
}); reject(new Error(`Socket error: ${error.message}`));
}; });
});
return result;
while (retries < this.maxRetries) {
try {
return await connect();
} catch (error) { } catch (error) {
retries++; retries++;
if (retries === this.maxRetries) { if (retries === maxRetries) {
throw new Error(`Failed to connect ${this.username} after ${this.maxRetries} attempts: ${error.message}`); throw error;
} }
console.warn(`Retry ${retries}/${this.maxRetries} for ${this.username}`); await new Promise(resolve => setTimeout(resolve, retryDelay));
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
} }
} }
} }
onConnected() {
// To be implemented by child classes
}
disconnect() { disconnect() {
if (this.socket) { if (this.socket) {
this.socket.disconnect(); this.socket.disconnect();

View file

@ -2,15 +2,20 @@
import { RoomParticipant } from './roomParticipant.js'; import { RoomParticipant } from './roomParticipant.js';
export class Student extends RoomParticipant { export class Student extends RoomParticipant {
nbrMessageReceived = 0;
constructor(username, roomName) { constructor(username, roomName) {
super(username, roomName); super(username, roomName);
} }
connectToRoom(baseUrl) { connectToRoom(baseUrl) {
return super.connectToRoom(baseUrl, () => { return super.connectToRoom(baseUrl);
this.joinRoom(); }
this.listenForTeacherMessage();
}); onConnected() {
this.joinRoom();
this.listenForTeacherMessage();
} }
joinRoom() { joinRoom() {
@ -25,6 +30,7 @@ export class Student extends RoomParticipant {
listenForTeacherMessage() { listenForTeacherMessage() {
if (this.socket) { if (this.socket) {
this.socket.on('message-sent-teacher', ({ message }) => { this.socket.on('message-sent-teacher', ({ message }) => {
this.nbrMessageReceived++;
this.respondToTeacher(message); this.respondToTeacher(message);
}); });
} }

View file

@ -1,22 +1,21 @@
import { RoomParticipant } from './roomParticipant.js'; import { RoomParticipant } from './roomParticipant.js';
export class Teacher extends RoomParticipant { export class Teacher extends RoomParticipant {
nbrMessageReceived = 0;
constructor(username, roomName) { constructor(username, roomName) {
super(username, roomName); super(username, roomName);
this.ready = false; this.ready = false;
} }
connectToRoom(baseUrl) { connectToRoom(baseUrl) {
return super.connectToRoom(baseUrl, () => { return super.connectToRoom(baseUrl);
this.createRoom(); }
this.listenForStudentMessage();
onConnected() {
// Add room creation confirmation listener this.createRoom();
this.socket.on('create-success', () => { this.listenForStudentMessage();
console.log(`Room ${this.roomName} created by teacher ${this.username}`);
this.ready = true;
});
});
} }
createRoom() { createRoom() {
@ -26,7 +25,7 @@ export class Teacher extends RoomParticipant {
} }
broadcastMessage(message) { broadcastMessage(message) {
if (this.socket && this.ready) { if (this.socket) {
this.socket.emit('message-from-teacher', { this.socket.emit('message-from-teacher', {
roomName: this.roomName, roomName: this.roomName,
message message
@ -40,6 +39,7 @@ export class Teacher extends RoomParticipant {
if (this.socket) { if (this.socket) {
this.socket.on('message-sent-student', ({ message }) => { this.socket.on('message-sent-student', ({ message }) => {
//console.log(`Teacher ${this.username} received: "${message}"`); //console.log(`Teacher ${this.username} received: "${message}"`);
this.nbrMessageReceived++;
}); });
} }
} }

View file

@ -9,8 +9,11 @@ export class Watcher extends RoomParticipant {
super(username, roomName); super(username, roomName);
} }
async connectToRoom(baseUrl) { connectToRoom(baseUrl) {
await super.connectToRoom(baseUrl); return super.connectToRoom(baseUrl);
}
onConnected() {
this.startCheckingResources(); this.startCheckingResources();
} }
@ -29,7 +32,7 @@ export class Watcher extends RoomParticipant {
} }
} }
startCheckingResources(intervalMs = 500) { startCheckingResources(intervalMs = 250) {
if (this.checkRessourceInterval) { if (this.checkRessourceInterval) {
console.warn(`Resource checking is already running for room ${this.roomName}.`); console.warn(`Resource checking is already running for room ${this.roomName}.`);
return; return;

View file

@ -7,18 +7,19 @@ import dotenv from 'dotenv';
// Load environment variables // Load environment variables
dotenv.config(); dotenv.config();
const BASE_URL = process.env.BASE_URL || 'http://localhost'; const BASE_URL = process.env.BASE_URL || 'http://msevignyl.duckdns.org';
const user = { const user = {
username: process.env.USER_EMAIL || 'admin@admin.com', username: process.env.USER_EMAIL || 'admin@admin.com',
password: process.env.USER_PASSWORD || 'admin' password: process.env.USER_PASSWORD || 'admin'
}; };
const numberRooms = parseInt(process.env.NUMBER_ROOMS || '5'); const numberRooms = parseInt(process.env.NUMBER_ROOMS || '50');
const usersPerRoom = parseInt(process.env.USERS_PER_ROOM || '60'); const usersPerRoom = parseInt(process.env.USERS_PER_ROOM || '60');
const roomAssociations = {}; const roomAssociations = {};
const maxMessages = parseInt(process.env.MAX_MESSAGES || '20'); const maxMessages = parseInt(process.env.MAX_MESSAGES || '20');
const conversationInterval = parseInt(process.env.CONVERSATION_INTERVAL || '1000'); const conversationInterval = parseInt(process.env.CONVERSATION_INTERVAL || '1000');
const batchSize = 10; // Number of simultaneous connections const batchSize = 5;
const batchDelay = 500; const batchDelay = 500;
const roomDelay = 1000;
/** /**
* Creates a room and immediately connects a teacher to it. * Creates a room and immediately connects a teacher to it.
@ -30,8 +31,6 @@ async function createRoomWithTeacher(token, index) {
throw new Error('Room creation failed'); throw new Error('Room creation failed');
} }
console.log(`Room ${index + 1} created with ID: ${room.id}`);
// Initialize room associations // Initialize room associations
roomAssociations[room.id] = { watcher: null, teacher: null, students: [] }; roomAssociations[room.id] = { watcher: null, teacher: null, students: [] };
@ -41,7 +40,6 @@ async function createRoomWithTeacher(token, index) {
// Connect teacher to room // Connect teacher to room
await teacher.connectToRoom(BASE_URL); await teacher.connectToRoom(BASE_URL);
console.log(`Teacher connected to room ${room.id}`);
return room.id; return room.id;
} catch (err) { } catch (err) {
@ -79,7 +77,6 @@ function addRemainingUsers() {
const participants = roomAssociations[roomId]; const participants = roomAssociations[roomId];
// Add watcher // Add watcher
console.log('Adding users to room ' + roomId);
participants.watcher = new Watcher(`watcher_${roomIndex}`, roomId); participants.watcher = new Watcher(`watcher_${roomIndex}`, roomId);
// Add students // Add students
@ -95,32 +92,49 @@ function addRemainingUsers() {
*/ */
async function connectRemainingParticipants(baseUrl) { async function connectRemainingParticipants(baseUrl) {
console.log('Connecting remaining participants in batches'); console.log('Connecting remaining participants in batches');
for (const [roomId, participants] of Object.entries(roomAssociations)) { for (const [roomId, participants] of Object.entries(roomAssociations)) {
console.log(`Processing room ${roomId}`); console.log(`Processing room ${roomId}`);
// Collect remaining participants for this room
const remainingParticipants = [ const remainingParticipants = [
participants.watcher, participants.watcher,
...participants.students ...participants.students
].filter(Boolean); ].filter(Boolean);
// Process participants in batches // Connect in smaller batches with longer delays
for (let i = 0; i < remainingParticipants.length; i += batchSize) { for (let i = 0; i < remainingParticipants.length; i += batchSize) {
const batch = remainingParticipants.slice(i, i + batchSize); const batch = remainingParticipants.slice(i, i + batchSize);
// Add connection timeout handling
const batchPromises = batch.map(participant => const batchPromises = batch.map(participant =>
participant.connectToRoom(baseUrl) Promise.race([
.catch(err => { participant.connectToRoom(baseUrl),
console.warn( new Promise((_, reject) =>
`Failed to connect ${participant.username} in room ${roomId}:`, setTimeout(() => reject(new Error('Connection timeout')), 10000)
err.message )
); ]).catch(err => {
return null; console.warn(
}) `Failed to connect ${participant.username} in room ${roomId}:`,
err.message
);
return null;
})
); );
await Promise.all(batchPromises); await Promise.all(batchPromises);
// Cleanup disconnected sockets
batch.forEach(participant => {
if (!participant.socket?.connected) {
participant.disconnect();
}
});
await new Promise(resolve => setTimeout(resolve, batchDelay)); await new Promise(resolve => setTimeout(resolve, batchDelay));
} }
// Add delay between rooms
await new Promise(resolve => setTimeout(resolve, roomDelay));
} }
console.log('Finished connecting remaining participants'); console.log('Finished connecting remaining participants');