Short term additional fix for #583

The previous commits do not handle all of the edge cases of #583
appropriately.  This is a short term solution that will work, but is not
as efficient as it could be.  The whole refreshAccount function needs to
be reconsidered and replaced with a more sane way of handling atomic
updates to the user's account state.
This commit is contained in:
calzoneman 2016-06-29 22:00:25 -07:00
parent c70dc83504
commit 312892e56b
4 changed files with 39 additions and 55 deletions

View File

@ -2,7 +2,7 @@
"author": "Calvin Montgomery", "author": "Calvin Montgomery",
"name": "CyTube", "name": "CyTube",
"description": "Online media synchronizer and chat", "description": "Online media synchronizer and chat",
"version": "3.17.3", "version": "3.17.4",
"repository": { "repository": {
"url": "http://github.com/calzoneman/sync" "url": "http://github.com/calzoneman/sync"
}, },

View File

@ -322,11 +322,9 @@ Channel.prototype.joinUser = function (user, data) {
return; return;
} }
function refreshUserAccount(cb) { user.channel = self;
user.refreshAccount({ if (self.is(Flags.C_REGISTERED)) {
channel: self.name, user.refreshAccount(function (err, account) {
name: user.getName()
}, function (err, account) {
if (err) { if (err) {
Logger.errlog.log("user.refreshAccount failed at Channel.joinUser"); Logger.errlog.log("user.refreshAccount failed at Channel.joinUser");
Logger.errlog.log(err.stack); Logger.errlog.log(err.stack);
@ -334,19 +332,8 @@ Channel.prototype.joinUser = function (user, data) {
return; return;
} }
if (cb) { afterAccount();
process.nextTick(cb);
}
}); });
}
if (self.is(Flags.C_REGISTERED) && user.is(Flags.U_LOGGED_IN)) {
refreshUserAccount(afterAccount);
} else if (self.is(Flags.C_REGISTERED)) {
user.waitFlag(Flags.U_LOGGED_IN, () => {
refreshUserAccount();
});
afterAccount();
} else { } else {
afterAccount(); afterAccount();
} }
@ -361,11 +348,9 @@ Channel.prototype.joinUser = function (user, data) {
self.checkModules("onUserPreJoin", [user, data], function (err, result) { self.checkModules("onUserPreJoin", [user, data], function (err, result) {
if (result === ChannelModule.PASSTHROUGH) { if (result === ChannelModule.PASSTHROUGH) {
if (user.account.channelRank !== user.account.globalRank) {
user.socket.emit("rank", user.account.effectiveRank);
}
self.acceptUser(user); self.acceptUser(user);
} else { } else {
user.channel = null;
user.account.channelRank = 0; user.account.channelRank = 0;
user.account.effectiveRank = user.account.globalRank; user.account.effectiveRank = user.account.globalRank;
self.refCounter.unref("Channel::user"); self.refCounter.unref("Channel::user");
@ -376,7 +361,6 @@ Channel.prototype.joinUser = function (user, data) {
}; };
Channel.prototype.acceptUser = function (user) { Channel.prototype.acceptUser = function (user) {
user.channel = this;
user.setFlag(Flags.U_IN_CHANNEL); user.setFlag(Flags.U_IN_CHANNEL);
user.socket.join(this.name); user.socket.join(this.name);
user.autoAFK(); user.autoAFK();

View File

@ -213,8 +213,8 @@ function handleConnection(sock) {
if (sock.user) { if (sock.user) {
user.setFlag(Flags.U_REGISTERED); user.setFlag(Flags.U_REGISTERED);
user.clearFlag(Flags.U_READY); user.clearFlag(Flags.U_READY);
user.refreshAccount({ name: sock.user.name }, user.account.name = sock.user.name;
function (err, account) { user.refreshAccount(function (err, account) {
if (err) { if (err) {
user.clearFlag(Flags.U_REGISTERED); user.clearFlag(Flags.U_REGISTERED);
user.setFlag(Flags.U_READY); user.setFlag(Flags.U_READY);

View File

@ -177,6 +177,10 @@ User.prototype.inChannel = function () {
return this.channel != null && !this.channel.dead; return this.channel != null && !this.channel.dead;
}; };
User.prototype.inRegisteredChannel = function () {
return this.inChannel() && this.channel.is(Flags.C_REGISTERED);
};
/* Called when a user's AFK status changes */ /* Called when a user's AFK status changes */
User.prototype.setAFK = function (afk) { User.prototype.setAFK = function (afk) {
if (!this.inChannel()) { if (!this.inChannel()) {
@ -283,17 +287,15 @@ User.prototype.login = function (name, pw) {
return; return;
} }
var opts = { name: user.name }; self.account.name = user.name;
if (self.inChannel()) {
opts.channel = self.channel.name;
}
self.setFlag(Flags.U_REGISTERED); self.setFlag(Flags.U_REGISTERED);
self.refreshAccount(opts, function (err, account) { self.refreshAccount(function (err, account) {
if (err) { if (err) {
Logger.errlog.log("[SEVERE] getAccount failed for user " + user.name); Logger.errlog.log("[SEVERE] getAccount failed for user " + user.name);
Logger.errlog.log(err); Logger.errlog.log(err);
self.clearFlag(Flags.U_REGISTERED); self.clearFlag(Flags.U_REGISTERED);
self.clearFlag(Flags.U_LOGGING_IN); self.clearFlag(Flags.U_LOGGING_IN);
self.account.name = "";
return; return;
} }
self.socket.emit("login", { self.socket.emit("login", {
@ -370,14 +372,12 @@ User.prototype.guestLogin = function (name) {
// Login succeeded // Login succeeded
lastguestlogin[self.realip] = Date.now(); lastguestlogin[self.realip] = Date.now();
var opts = { name: name }; self.account.name = name;
if (self.inChannel()) { self.refreshAccount(function (err, account) {
opts.channel = self.channel.name;
}
self.refreshAccount(opts, function (err, account) {
if (err) { if (err) {
Logger.errlog.log("[SEVERE] getAccount failed for guest login " + name); Logger.errlog.log("[SEVERE] getAccount failed for guest login " + name);
Logger.errlog.log(err); Logger.errlog.log(err);
self.account.name = "";
return; return;
} }
@ -409,29 +409,28 @@ setInterval(function () {
} }
}, 5 * 60 * 1000); }, 5 * 60 * 1000);
User.prototype.refreshAccount = function (opts, cb) { User.prototype.refreshAccount = function (cb) {
if (!cb) { var name = this.account.name;
cb = opts; var opts = {
opts = {}; registered: this.is(Flags.U_REGISTERED),
} channel: this.inRegisteredChannel() ? this.channel.name : false
};
var different = false;
for (var key in opts) {
if (opts[key] !== this.account[key]) {
different = true;
break;
}
}
if (!different) {
return;
}
var name = ("name" in opts) ? opts.name : this.account.name;
opts.registered = this.is(Flags.U_REGISTERED);
var self = this; var self = this;
var old = this.account; var old = this.account;
Account.getAccount(name, this.realip, opts, function (err, account) { Account.getAccount(name, this.realip, opts, function (err, account) {
// TODO
//
// This is a hack to fix #583, an issue where racing callbacks
// from refreshAccount() can cause the user's rank to get out
// of sync. Ideally this should be removed in favor of a more
// robust way of handling updating account state, perhaps a mutex.
if (self.is(Flags.U_REGISTERED) !== opts.registered ||
(self.inRegisteredChannel() && !opts.channel) ||
self.account.name !== name) {
self.refreshAccount(cb);
return;
}
if (!err) { if (!err) {
/* Update account if anything changed in the meantime */ /* Update account if anything changed in the meantime */
for (var key in old) { for (var key in old) {
@ -445,7 +444,8 @@ User.prototype.refreshAccount = function (opts, cb) {
self.emit("effectiveRankChange", self.account.effectiveRank); self.emit("effectiveRankChange", self.account.effectiveRank);
} }
} }
cb(err, account);
process.nextTick(cb, err, account);
}); });
}; };