'use strict'
const co = require('co')
const defer = require('co-defer')
const MAX_32INT = 2147483647
/**
* Callback to process a notification.
* @callback processNotification
* @param {Object} notification
* @return {Promise}
*/
/**
* @param {Object} options
* @param {Class} options.Notification
* @param {Object} options.knex
* @param {Logger} options.log
* @param {processNotification} options.processNotification
*/
function NotificationScheduler (options) {
this.notificationDAO = options.notificationDAO
this.log = options.log
this.processNotification = options.processNotification
this._timeout = null
}
NotificationScheduler.prototype.isEnabled = function () { return !!this._timeout }
NotificationScheduler.prototype.start = function () {
if (this._timeout) return
this._timeout = defer.setTimeout(this.processQueue.bind(this), 0)
}
NotificationScheduler.prototype.stop = function () {
if (!this._timeout) return
clearTimeout(this._timeout)
this._timeout = null
}
// Be sure to call this to schedule any retries, otherwise they will never be executed.
NotificationScheduler.prototype.scheduleProcessing = co.wrap(function * () {
if (!this._timeout) return
clearTimeout(this._timeout)
const timeToEarliestNotification = yield this._getTimeToEarliestNotification()
if (!timeToEarliestNotification) return
this._timeout = defer.setTimeout(
this.processQueue.bind(this),
timeToEarliestNotification)
})
NotificationScheduler.prototype.retryNotification = function (notification) {
const retries = notification.retry_count = (notification.retry_count || 0) + 1
// Exponential backoff
const delay = Math.pow(2, retries)
// If delay becomes really long (over 1 week), give up.
if (delay >= 60 * 60 * 24 * 7) {
this.log.debug('Give up on notification ' + notification.id)
return notification.destroy()
}
// Add random jitter
notification.retry_at = new Date(Date.now() + Math.round(1000 * delay * (0.8 + Math.random() * 0.4)))
this.log.debug('Notification ' + notification.id + ' new retry_count: ' + notification.retry_count + ', new retry_at: ' + notification.retry_at)
return this.notificationDAO.updateNotification(notification)
}
NotificationScheduler.prototype.processQueue = function * () {
const notifications = yield this.notificationDAO.getReadyNotifications()
this.log.debug('processing ' + notifications.length + ' notifications')
yield notifications.map(this.processNotification.bind(this))
yield this.scheduleProcessing()
}
NotificationScheduler.prototype._getTimeToEarliestNotification = function * () {
// Get Date.now() for the diff _before_ calling _getEarliestNotification()
// to ensure that the diff is >=0.
const now = Date.now()
const earliestNotification =
yield this.notificationDAO.getEarliestNotification()
const retryAt = earliestNotification && earliestNotification.retry_at
if (!retryAt) return
return Math.min(retryAt - now, MAX_32INT)
}
module.exports = NotificationScheduler