diff --git a/package.json b/package.json index 175451e3..9dcde330 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "cheerio": "^0.19.0", "compression": "^1.5.2", "cookie-parser": "^1.4.0", + "create-error": "^0.3.1", "csrf": "^3.0.0", "cytube-mediaquery": "git://github.com/CyTube/mediaquery", "cytubefilters": "git://github.com/calzoneman/cytubefilters#095b7956", diff --git a/src/bgtask.js b/src/bgtask.js index 02aca717..a966f60d 100644 --- a/src/bgtask.js +++ b/src/bgtask.js @@ -64,7 +64,9 @@ function initChannelDumper(Server) { for (var i = 0; i < Server.channels.length; i++) { var chan = Server.channels[i]; if (!chan.dead && chan.users && chan.users.length > 0) { - chan.saveState(); + chan.saveState().catch(err => { + Logger.errlog.log(`Failed to save /r/${chan.name}: ${err.stack}`); + }); } } }, CHANNEL_SAVE_INTERVAL); diff --git a/src/channel-storage/channelstore.js b/src/channel-storage/channelstore.js index 53d93118..fa8a1678 100644 --- a/src/channel-storage/channelstore.js +++ b/src/channel-storage/channelstore.js @@ -1,6 +1,6 @@ import { FileStore } from './filestore'; -var CHANNEL_STORE = new FileStore(); +const CHANNEL_STORE = new FileStore(); export function load(channelName) { return CHANNEL_STORE.load(channelName); diff --git a/src/channel-storage/filestore.js b/src/channel-storage/filestore.js index 1d5053d1..515af434 100644 --- a/src/channel-storage/filestore.js +++ b/src/channel-storage/filestore.js @@ -2,6 +2,7 @@ import * as Promise from 'bluebird'; import { stat } from 'fs'; import * as fs from 'graceful-fs'; import path from 'path'; +import { ChannelStateSizeError } from '../errors'; const readFileAsync = Promise.promisify(fs.readFile); const writeFileAsync = Promise.promisify(fs.writeFile); @@ -18,7 +19,10 @@ export class FileStore { const filename = this.filenameForChannel(channelName); return statAsync(filename).then(stats => { if (stats.size > SIZE_LIMIT) { - throw new Error('Channel state file is too large: ' + stats.size); + throw new ChannelStateSizeError('Channel state file is too large', { + limit: SIZE_LIMIT, + actual: stats.size + }); } else { return readFileAsync(filename); } @@ -34,11 +38,12 @@ export class FileStore { save(channelName, data) { const filename = this.filenameForChannel(channelName); const fileContents = new Buffer(JSON.stringify(data), 'utf8'); - if (fileContents.length > SIZE_LIMIT) { - let error = new Error('Channel state size is too large'); - error.limit = SIZE_LIMIT; - error.size = fileContents.length; - return Promise.reject(error); + if (fileContents.length > 0*SIZE_LIMIT) { + return Promise.reject(new ChannelStateSizeError( + 'Channel state size is too large', { + limit: SIZE_LIMIT, + actual: fileContents.length + })); } return writeFileAsync(filename, fileContents); diff --git a/src/channel/channel.js b/src/channel/channel.js index 88cd1cb6..7bfccc7c 100644 --- a/src/channel/channel.js +++ b/src/channel/channel.js @@ -8,10 +8,9 @@ var fs = require("graceful-fs"); var path = require("path"); var sio = require("socket.io"); var db = require("../database"); -var ChannelStore = require("../channel-storage/channelstore"); -var Promise = require("bluebird"); - -const SIZE_LIMIT = 1048576; +import * as ChannelStore from '../channel-storage/channelstore'; +import { ChannelStateSizeError } from '../errors'; +import * as Promise from 'bluebird'; /** * Previously, async channel functions were riddled with race conditions due to @@ -180,6 +179,13 @@ Channel.prototype.loadState = function () { } }); this.setFlag(Flags.C_READY); + }).catch(ChannelStateSizeError, err => { + const message = "This channel's state size has exceeded the memory limit " + + "enforced by this server. Please contact an administrator " + + "for assistance."; + + Logger.errlog.log(err.stack); + errorLoad(message); }).catch(err => { if (err.code === 'ENOENT') { Object.keys(this.modules).forEach(m => { @@ -187,21 +193,14 @@ Channel.prototype.loadState = function () { }); this.setFlag(Flags.C_READY); return; - } - - let message; - if (/Channel state file is too large/.test(err.message)) { - message = "This channel's state size has exceeded the memory limit " + - "enforced by this server. Please contact an administrator " + - "for assistance."; } else { - message = "An error occurred when loading this channel's data from " + + const message = "An error occurred when loading this channel's data from " + "disk. Please contact an administrator for assistance. " + `The error was: ${err}`; - } - Logger.errlog.log(err.stack); - errorLoad(message); + Logger.errlog.log(err.stack); + errorLoad(message); + } }); }; @@ -220,22 +219,17 @@ Channel.prototype.saveState = function () { this.modules[m].save(data); }); - return ChannelStore.save(this.uniqueName, data).catch(err => { - if (/Channel state size is too large/.test(err.message)) { - this.users.forEach(u => { - if (u.account.effectiveRank >= 2) { - u.socket.emit("warnLargeChandump", { - limit: err.limit, - actual: err.size - }); - } - }); + return ChannelStore.save(this.uniqueName, data).catch(ChannelStateSizeError, err => { + this.users.forEach(u => { + if (u.account.effectiveRank >= 2) { + u.socket.emit("warnLargeChandump", { + limit: err.limit, + actual: err.actual + }); + } + }); - Logger.errlog.log(`Not saving ${this.uniqueName} because it exceeds ` + - "the size limit"); - } else { - Logger.errlog.log(`Failed to save ${this.uniqueName}: ${err.stack}`); - } + throw err; }); }; diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 00000000..ee3a9ce7 --- /dev/null +++ b/src/errors.js @@ -0,0 +1,3 @@ +import createError from 'create-error'; + +export const ChannelStateSizeError = createError('ChannelStateSizeError'); diff --git a/src/server.js b/src/server.js index fcdddd8e..79250e56 100644 --- a/src/server.js +++ b/src/server.js @@ -236,6 +236,9 @@ Server.prototype.shutdown = function () { }).then(() => { Logger.syslog.log("Goodbye"); process.exit(0); + }).catch(err => { + Logger.errlog.log(`Caught error while saving channels: ${err.stack}`); + process.exit(1); }); };