"use strict"; /** * This is an internal module which manages queuing, scheduling and retrying * of requests. * @module scheduler */ var utils = require("./utils"); /** * Construct a scheduler for Matrix. * @constructor * @param {module:scheduler~retryAlgorithm} retryAlgorithm Optional. The retry * algorithm to use. * @param {module:scheduler~queueAlgorithm} queueAlgorithm Optional. The queuing * algorithm to use. * @prop {module:scheduler~retryAlgorithm} retryAlgorithm The retry algorithm to * apply when determining when to try to send an event again. Defaults to * {@link module:scheduler~MatrixScheduler.RETRY_BACKOFF_RATELIMIT}. * @prop {module:scheduler~queueAlgorithm} queueAlgorithm The queuing algorithm * to apply when determining which events should be sent before the given event. * Defaults to {@link module:scheduler~MatrixScheduler.QUEUE_MESSAGES}. */ function MatrixScheduler(retryAlgorithm, queueAlgorithm) { this.retryAlgorithm = retryAlgorithm || MatrixScheduler.RETRY_BACKOFF_RATELIMIT; this.queueAlgorithm = queueAlgorithm || MatrixScheduler.QUEUE_MESSAGES; this._queues = { // queueName: [MatrixEvent, ...] }; } /** * Remove the head of the queue. * @param {string} queueName The name of the queue to get the event from. * @return {MatrixEvent} The head of the queue or null. */ MatrixScheduler.prototype.removeNextEvent = function(queueName) { var queue = this._queues[queueName]; if (!utils.isArray(queue)) { return null; } return queue[0]; }; /** * Add an event to the end of the queue. * @param {string} queueName The name of the queue to add the event to. * @param {MatrixEvent} event The event to queue. */ MatrixScheduler.prototype.addEventToQueue = function(queueName, event) { if (!this._queues[queueName]) { this._queues[queueName] = []; } this._queues[queueName].push(event); }; /** * Queue an event if it is required. * @param {MatrixEvent} event The event that may be queued. * @return {Promise} A promise which will be resolved when the event is sent, if * it has been added to a queue, else null. */ MatrixScheduler.prototype.queueEvent = function(event) { var queueName = this.queueAlgorithm(event); if (!queueName) { return null; } this.addEventToQueue(queueName, event); // TODO: Return a promise }; /** * Retries events up to 4 times using exponential backoff. This produces wait * times of 2, 4, 8, and 16 seconds (30s total) after which we give up. If the * failure was due to a rate limited request, the time specified in the error is * waited before being retried. * @param {MatrixEvent} event * @param {Number} attempts * @param {MatrixError} err * @return {Number} * @see module:scheduler~retryAlgorithm */ MatrixScheduler.RETRY_BACKOFF_RATELIMIT = function(event, attempts, err) { if (err.name === "M_LIMIT_EXCEEDED") { var waitTime = err.data.retry_after_ms; if (waitTime) { return waitTime; } } if (attempts > 4) { return -1; // give up } return (1000 * Math.pow(2, attempts)); }; /** * Queues m.room.message events and lets other events continue * concurrently. * @param {MatrixEvent} event * @return {string} * @see module:scheduler~queueAlgorithm */ MatrixScheduler.QUEUE_MESSAGES = function(event) { if (event.getType() === "m.room.message") { // put these events in the 'message' queue. return "message"; } // allow all other events continue concurrently. return null; }; /** * The retry algorithm to apply when retrying events. To stop retrying, return * -1. If this event was part of a queue, it will be removed from * the queue. * @callback retryAlgorithm * @param {MatrixEvent} event The event being retried. * @param {Number} attempts The number of failed attempts. This will always be * >= 1. * @param {MatrixError} err The most recent error message received when trying * to send this event. * @return {Number} The number of milliseconds to wait before trying again. If * this is 0, the request will be immediately retried. If this is * -1, the event will be marked as * {@link module:models/event.EventStatus.NOT_SENT} and will not be retried. */ /** * The queuing algorithm to apply to events. All queues created are serviced in * a FIFO manner. To send the event ASAP, return null which will * not put this event in a queue. Events that fail to send that form part of * a queue will be removed from the queue and the next event in the queue will * be sent. * @callback queueAlgorithm * @param {MatrixEvent} event The event to be sent. * @return {string} The name of the queue to put the event into. If a queue with * this name does not exist, it will be created. If this is null, * the event is not put into a queue and will be sent concurrently. */ /** * The MatrixScheduler class. */ module.exports = MatrixScheduler;