Fix an extremely rare race condition in battle preparation

By the way promises work, the `team` property assigned in User#finishPrepBattle()
is not guaranteed to remain the same by the time the resolution callback is called.

To fix this, the packed team is now directly passed to the callback instead.
This commit is contained in:
Ivo Julca 2017-08-07 06:51:21 -05:00
parent 31fe2aad7e
commit 36ab0a2e57
4 changed files with 30 additions and 16 deletions

View File

@ -3265,8 +3265,10 @@ exports.commands = {
return false;
}
}
user.prepBattle(Dex.getFormat(target).id, 'challenge', connection).then(result => {
if (result) user.makeChallenge(targetUser, target);
user.prepBattle(Dex.getFormat(target).id, 'challenge', connection).then(validTeam => {
if (validTeam === false) return;
user.team = validTeam;
user.makeChallenge(targetUser, target);
});
},
@ -3309,8 +3311,10 @@ exports.commands = {
this.popupReply(target + " isn't challenging you - maybe they cancelled before you could accept?");
return false;
}
user.prepBattle(Dex.getFormat(format).id, 'challenge', connection).then(result => {
if (result) user.acceptChallengeFrom(userid);
user.prepBattle(Dex.getFormat(format).id, 'challenge', connection).then(validTeam => {
if (validTeam === false) return;
user.team = validTeam;
user.acceptChallengeFrom(userid);
});
},

View File

@ -62,13 +62,13 @@ class Matchmaker {
if (!user.connected) return;
formatid = Dex.getFormat(formatid).id;
return user.prepBattle(formatid, 'search', null)
.then(result => this.finishSearchBattle(user, formatid, result));
.then(validTeam => this.finishSearchBattle(user, formatid, validTeam));
}
finishSearchBattle(user, formatid, result) {
if (!result) return;
finishSearchBattle(user, formatid, validTeam) {
if (validTeam === false) return;
const search = new Search(user.userid, user.team);
const search = new Search(user.userid, validTeam);
// Get the user's rating before actually starting to search.
Ladders(formatid).getRating(user.userid).then(rating => {
search.setRating(rating);

View File

@ -712,11 +712,11 @@ class Tournament {
this.isAvailableMatchesInvalidated = true;
this.update();
user.prepBattle(this.teambuilderFormat, 'tournament', user, this.banlist).then(result => this.finishChallenge(user, to, output, result));
user.prepBattle(this.teambuilderFormat, 'tournament', user, this.banlist).then(validTeam => this.finishChallenge(user, to, output, validTeam));
}
finishChallenge(user, to, output, result) {
finishChallenge(user, to, output, validTeam) {
let from = this.players[user.userid];
if (!result) {
if (validTeam === false) {
this.generator.setUserBusy(from, false);
this.generator.setUserBusy(to, false);
@ -726,8 +726,8 @@ class Tournament {
}
this.lastActionTimes.set(to, Date.now());
this.pendingChallenges.set(from, {to: to, team: user.team});
this.pendingChallenges.set(to, {from: from, team: user.team});
this.pendingChallenges.set(from, {to: to, team: validTeam});
this.pendingChallenges.set(to, {from: from, team: validTeam});
from.sendRoom('|tournament|update|' + JSON.stringify({challenging: to.name}));
to.sendRoom('|tournament|update|' + JSON.stringify({challenged: from.name}));

View File

@ -1283,16 +1283,26 @@ class User {
}
return TeamValidator(formatid, customBanlist).prepTeam(this.team, this.locked || this.namelocked).then(result => this.finishPrepBattle(connection, result));
}
/**
* Parses the result of a team validation and notifies the user.
*
* @param {Connection} connection - The connection from which the team validation was requested.
* @param {string} result - The raw result received by the team validator.
* @return {string|boolean} - The packed team if the validation was successful. False otherwise.
*/
finishPrepBattle(connection, result) {
if (result.charAt(0) !== '1') {
connection.popup(`Your team was rejected for the following reasons:\n\n- ` + result.slice(1).replace(/\n/g, `\n- `));
return false;
}
if (result.length > 1) {
this.team = result.slice(1);
if (this !== users.get(this.userid)) {
// TODO: User feedback.
return false;
}
return (this === users.get(this.userid));
return result.slice(1);
}
updateChallenges() {
let challengeTo = this.challengeTo;