mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Implementing retries on failed user updates when finishing a quest (#8251)
* Implementing retries on failed user updates when finishing a quest. fixes #8035 * Refactoring mongo db retries to use the same as code path as original call and moving retries to count based over time based. * Adding tests for retry logic and updating retries to happen recursively * Moving callbacks to promises and other tweaks according to pr. * Chaging mongoose promise to use .catch() functionality * If all retries fail, the system will now throw an error instead of returning an error message.
This commit is contained in:
@@ -1061,8 +1061,45 @@ describe('Group Model', () => {
|
|||||||
[nonParticipatingMember._id]: false,
|
[nonParticipatingMember._id]: false,
|
||||||
[undecidedMember._id]: null,
|
[undecidedMember._id]: null,
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
sandbox.spy(User, 'update');
|
describe('user update retry failures', () => {
|
||||||
|
let successfulMock = {
|
||||||
|
exec: () => {
|
||||||
|
return Promise.resolve({raw: 'sucess'});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let failedMock = {
|
||||||
|
exec: () => {
|
||||||
|
return Promise.reject(new Error('error'));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('doesn\'t retry successful operations', async () => {
|
||||||
|
sandbox.stub(User, 'update').returns(successfulMock);
|
||||||
|
|
||||||
|
await party.finishQuest(quest);
|
||||||
|
|
||||||
|
expect(User.update).to.be.calledTwice;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stops retrying when a successful update has occurred', async () => {
|
||||||
|
let updateStub = sandbox.stub(User, 'update');
|
||||||
|
updateStub.onCall(0).returns(failedMock);
|
||||||
|
updateStub.returns(successfulMock);
|
||||||
|
|
||||||
|
await party.finishQuest(quest);
|
||||||
|
|
||||||
|
expect(User.update).to.be.calledThrice;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retries failed updates at most five times per user', async () => {
|
||||||
|
sandbox.stub(User, 'update').returns(failedMock);
|
||||||
|
|
||||||
|
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
|
||||||
|
|
||||||
|
expect(User.update.callCount).to.eql(10);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gives out achievements', async () => {
|
it('gives out achievements', async () => {
|
||||||
@@ -1171,13 +1208,15 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
context('Party quests', () => {
|
context('Party quests', () => {
|
||||||
it('updates participating members with rewards', async () => {
|
it('updates participating members with rewards', async () => {
|
||||||
|
sandbox.spy(User, 'update');
|
||||||
await party.finishQuest(quest);
|
await party.finishQuest(quest);
|
||||||
|
|
||||||
expect(User.update).to.be.calledOnce;
|
expect(User.update).to.be.calledTwice;
|
||||||
expect(User.update).to.be.calledWithMatch({
|
expect(User.update).to.be.calledWithMatch({
|
||||||
_id: {
|
_id: questLeader._id,
|
||||||
$in: [questLeader._id, participatingMember._id],
|
});
|
||||||
},
|
expect(User.update).to.be.calledWithMatch({
|
||||||
|
_id: participatingMember._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1204,6 +1243,7 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates all users with rewards', async () => {
|
it('updates all users with rewards', async () => {
|
||||||
|
sandbox.spy(User, 'update');
|
||||||
await party.finishQuest(tavernQuest);
|
await party.finishQuest(tavernQuest);
|
||||||
|
|
||||||
expect(User.update).to.be.calledOnce;
|
expect(User.update).to.be.calledOnce;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const LARGE_GROUP_COUNT_MESSAGE_CUTOFF = shared.constants.LARGE_GROUP_COUNT_MESS
|
|||||||
|
|
||||||
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
|
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
|
||||||
const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
|
const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
|
||||||
|
const MAX_UPDATE_RETRIES = 5;
|
||||||
|
|
||||||
export let schema = new Schema({
|
export let schema = new Schema({
|
||||||
name: {type: String, required: true},
|
name: {type: String, required: true},
|
||||||
@@ -577,6 +578,19 @@ schema.statics.cleanGroupQuest = function cleanGroupQuest () {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function _updateUserWithRetries (userId, updates, numTry = 1) {
|
||||||
|
return await User.update({_id: userId}, updates).exec()
|
||||||
|
.then((raw) => {
|
||||||
|
return raw;
|
||||||
|
}).catch((err) => {
|
||||||
|
if (numTry < MAX_UPDATE_RETRIES) {
|
||||||
|
return _updateUserWithRetries(userId, updates, ++numTry);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Participants: Grant rewards & achievements, finish quest.
|
// Participants: Grant rewards & achievements, finish quest.
|
||||||
// Changes the group object update members
|
// Changes the group object update members
|
||||||
schema.methods.finishQuest = async function finishQuest (quest) {
|
schema.methods.finishQuest = async function finishQuest (quest) {
|
||||||
@@ -623,11 +637,19 @@ schema.methods.finishQuest = async function finishQuest (quest) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let q = this._id === TAVERN_ID ? {} : {_id: {$in: this.getParticipatingQuestMembers()}};
|
let participants = this._id === TAVERN_ID ? {} : this.getParticipatingQuestMembers();
|
||||||
this.quest = {};
|
this.quest = {};
|
||||||
this.markModified('quest');
|
this.markModified('quest');
|
||||||
|
|
||||||
return await User.update(q, updates, {multi: true}).exec();
|
if (this._id === TAVERN_ID) {
|
||||||
|
return await User.update({}, updates, {multi: true}).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
let promises = participants.map(userId => {
|
||||||
|
return _updateUserWithRetries(userId, updates);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Bluebird.all(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
function _isOnQuest (user, progress, group) {
|
function _isOnQuest (user, progress, group) {
|
||||||
|
|||||||
Reference in New Issue
Block a user