diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index e07d994..bbd9a03 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -37,7 +37,8 @@ var config = 'opera' ], // Socket.io request timeout - requestTimeout : 10000, + requestTimeout : 20000, + requestRetries : 3, transportOptions : { tcp : true diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 1bf75c6..16f5b6b 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1,6 +1,7 @@ import Logger from './Logger'; import hark from 'hark'; import { getSignalingUrl } from './urlFactory'; +import { SocketTimeoutError } from './utils'; import * as requestActions from './actions/requestActions'; import * as meActions from './actions/meActions'; import * as roomActions from './actions/roomActions'; @@ -574,7 +575,7 @@ export default class RoomClient if (called) return; called = true; - callback(new Error('Request timeout.')); + callback(new SocketTimeoutError('Request timed out')); }, ROOM_OPTIONS.requestTimeout ); @@ -590,13 +591,13 @@ export default class RoomClient }; } - sendRequest(method, data) + _sendRequest(method, data) { return new Promise((resolve, reject) => { if (!this._signalingSocket) { - reject('No socket connection.'); + reject('No socket connection'); } else { @@ -606,19 +607,42 @@ export default class RoomClient this.timeoutCallback((err, response) => { if (err) - { reject(err); - } else - { resolve(response); - } }) ); } }); } + async sendRequest(method, data) + { + logger.debug('sendRequest() [method:"%s", data:"%o"]', method, data); + + const { + requestRetries = 3 + } = window.config; + + for (let tries = 0; tries < requestRetries; tries++) + { + try + { + return await this._sendRequest(method, data); + } + catch (error) + { + if ( + error instanceof SocketTimeoutError && + tries < requestRetries + ) + logger.warn('sendRequest() | timeout, retrying [attempt:"%s"]', tries); + else + throw error; + } + } + } + async changeDisplayName(displayName) { logger.debug('changeDisplayName() [displayName:"%s"]', displayName); diff --git a/app/src/utils.js b/app/src/utils.js index 1fba8b3..76e88db 100644 --- a/app/src/utils.js +++ b/app/src/utils.js @@ -16,4 +16,22 @@ export const idle = (callback, delay) => handle = setTimeout(callback, delay); }; -}; \ No newline at end of file +}; + +/** + * Error produced when a socket request has a timeout. + */ +export class SocketTimeoutError extends Error +{ + constructor(message) + { + super(message); + + this.name = 'SocketTimeoutError'; + + if (Error.hasOwnProperty('captureStackTrace')) // Just in V8. + Error.captureStackTrace(this, SocketTimeoutError); + else + this.stack = (new Error(message)).stack; + } +} \ No newline at end of file diff --git a/server/config/config.example.js b/server/config/config.example.js index e08f910..6ca4a27 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -276,6 +276,10 @@ module.exports = // maxUsersPerRoom : 20, // Room size before spreading to new router routerScaleSize : 40, + // Socket timout value + requestTimeout : 20000, + // Socket retries when timeout + requestRetries : 3, // Mediasoup settings mediasoup : { diff --git a/server/lib/Room.js b/server/lib/Room.js index f110327..0e58c23 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -3,6 +3,7 @@ const AwaitQueue = require('awaitqueue'); const axios = require('axios'); const Logger = require('./Logger'); const Lobby = require('./Lobby'); +const { SocketTimeoutError } = require('./errors'); const { v4: uuidv4 } = require('uuid'); const jwt = require('jsonwebtoken'); const userRoles = require('../userRoles'); @@ -1759,9 +1760,9 @@ class Room extends EventEmitter if (called) return; called = true; - callback(new Error('Request timeout.')); + callback(new SocketTimeoutError('Request timed out')); }, - 10000 + config.requestTimeout || 20000 ); return (...args) => @@ -1775,7 +1776,7 @@ class Room extends EventEmitter }; } - _request(socket, method, data = {}) + _sendRequest(socket, method, data = {}) { return new Promise((resolve, reject) => { @@ -1797,6 +1798,33 @@ class Room extends EventEmitter }); } + async _request(socket, method, data) + { + logger.debug('_request() [method:"%s", data:"%o"]', method, data); + + const { + requestRetries = 3 + } = config; + + for (let tries = 0; tries < requestRetries; tries++) + { + try + { + return await this._sendRequest(socket, method, data); + } + catch (error) + { + if ( + error instanceof SocketTimeoutError && + tries < requestRetries + ) + logger.warn('_request() | timeout, retrying [attempt:"%s"]', tries); + else + throw error; + } + } + } + _notification(socket, method, data = {}, broadcast = false, includeSender = false) { if (broadcast) diff --git a/server/lib/errors.js b/server/lib/errors.js new file mode 100644 index 0000000..379838d --- /dev/null +++ b/server/lib/errors.js @@ -0,0 +1,22 @@ +/** + * Error produced when a socket request has a timeout. + */ +class SocketTimeoutError extends Error +{ + constructor(message) + { + super(message); + + this.name = 'SocketTimeoutError'; + + if (Error.hasOwnProperty('captureStackTrace')) // Just in V8. + Error.captureStackTrace(this, SocketTimeoutError); + else + this.stack = (new Error(message)).stack; + } +} + +module.exports = +{ + SocketTimeoutError +}; \ No newline at end of file