1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-12-08 19:08:34 +03:00

Make Event.attemptDecryption return useful promises

Even if a decryption attempt is in progress, return a promise which blocks
until the attempt is complete.
This commit is contained in:
Richard van der Hoff
2017-08-14 18:38:29 +01:00
parent b26b1caa86
commit 38c9a05a0c
2 changed files with 39 additions and 18 deletions

View File

@@ -43,6 +43,8 @@ describe("MatrixEvent", () => {
it('should retry decryption if a retry is queued', () => { it('should retry decryption if a retry is queued', () => {
let callCount = 0; let callCount = 0;
let prom2;
const crypto = { const crypto = {
decryptEvent: function() { decryptEvent: function() {
++callCount; ++callCount;
@@ -50,12 +52,15 @@ describe("MatrixEvent", () => {
if (callCount == 1) { if (callCount == 1) {
// schedule a second decryption attempt while // schedule a second decryption attempt while
// the first one is still running. // the first one is still running.
encryptedEvent.attemptDecryption(crypto); prom2 = encryptedEvent.attemptDecryption(crypto);
const error = new Error("nope"); const error = new Error("nope");
error.name = 'DecryptionError'; error.name = 'DecryptionError';
return Promise.reject(error); return Promise.reject(error);
} else { } else {
expect(prom2.isFulfilled()).toBe(
false, 'second attemptDecryption resolved too soon');
return Promise.resolve({ return Promise.resolve({
clearEvent: { clearEvent: {
type: 'm.room.message', type: 'm.room.message',
@@ -68,6 +73,9 @@ describe("MatrixEvent", () => {
return encryptedEvent.attemptDecryption(crypto).then(() => { return encryptedEvent.attemptDecryption(crypto).then(() => {
expect(callCount).toEqual(2); expect(callCount).toEqual(2);
expect(encryptedEvent.getType()).toEqual('m.room.message'); expect(encryptedEvent.getType()).toEqual('m.room.message');
// make sure the second attemptDecryption resolves
return prom2;
}); });
}); });
}); });

View File

@@ -21,9 +21,9 @@ limitations under the License.
* @module models/event * @module models/event
*/ */
const EventEmitter = require("events").EventEmitter; import Promise from 'bluebird';
import {EventEmitter} from 'events';
const utils = require('../utils.js'); import utils from '../utils.js';
/** /**
* Enum for event statuses. * Enum for event statuses.
@@ -129,8 +129,10 @@ module.exports.MatrixEvent = function MatrixEvent(
*/ */
this._forwardingCurve25519KeyChain = []; this._forwardingCurve25519KeyChain = [];
/* flag to indicate if we have a process decrypting this event */ /* if we have a process decrypting this event, a Promise which resolves
this._decrypting = false; * when it is finished. Normally null.
*/
this._decryptionPromise = null;
/* flag to indicate if we should retry decrypting this event after the /* flag to indicate if we should retry decrypting this event after the
* first attempt (eg, we have received new data which means that a second * first attempt (eg, we have received new data which means that a second
@@ -313,7 +315,7 @@ utils.extend(module.exports.MatrixEvent.prototype, {
* @return {boolean} True if this event is currently being decrypted, else false. * @return {boolean} True if this event is currently being decrypted, else false.
*/ */
isBeingDecrypted: function() { isBeingDecrypted: function() {
return this._decrypting; return this._decryptionPromise != null;
}, },
/** /**
@@ -325,7 +327,7 @@ utils.extend(module.exports.MatrixEvent.prototype, {
* *
* @param {module:crypto} crypto crypto module * @param {module:crypto} crypto crypto module
* *
* returns a promise which resolves (to undefined) when the decryption * @returns {Promise} promise which resolves (to undefined) when the decryption
* attempt is completed. * attempt is completed.
*/ */
attemptDecryption: async function(crypto) { attemptDecryption: async function(crypto) {
@@ -350,15 +352,25 @@ utils.extend(module.exports.MatrixEvent.prototype, {
// attempts going at the same time, so just set a flag that says we have // attempts going at the same time, so just set a flag that says we have
// new info. // new info.
// //
if (this._decrypting) { if (this._decryptionPromise) {
console.log( console.log(
`Event ${this.getId()} already being decrypted; queueing a retry`, `Event ${this.getId()} already being decrypted; queueing a retry`,
); );
this._retryDecryption = true; this._retryDecryption = true;
return; return this._decryptionPromise;
} }
this._decrypting = true; this._decryptionPromise = this._decryptionLoop(crypto);
return this._decryptionPromise;
},
_decryptionLoop: async function(crypto) {
// make sure that this method never runs completely synchronously.
// (doing so would mean that we would clear _decryptionPromise *before*
// it is set in attemptDecryption - and hence end up with a stuck
// `_decryptionPromise`).
await Promise.resolve();
while (true) { while (true) {
this._retryDecryption = false; this._retryDecryption = false;
@@ -376,7 +388,7 @@ utils.extend(module.exports.MatrixEvent.prototype, {
console.error( console.error(
`Error decrypting event (id=${this.getId()}): ${e.stack || e}`, `Error decrypting event (id=${this.getId()}): ${e.stack || e}`,
); );
this._decrypting = false; this._decryptionPromise = null;
this._retryDecryption = false; this._retryDecryption = false;
return; return;
} }
@@ -384,15 +396,16 @@ utils.extend(module.exports.MatrixEvent.prototype, {
// see if we have a retry queued. // see if we have a retry queued.
// //
// NB: make sure to keep this check in the same tick of the // NB: make sure to keep this check in the same tick of the
// event loop as `_decrypting = false` below - otherwise we // event loop as `_decryptionPromise = null` below - otherwise we
// risk a race: // risk a race:
// //
// * A: we check _retryDecryption here and see that it is // * A: we check _retryDecryption here and see that it is
// false // false
// * B: we get a second call to attemptDecryption, which sees // * B: we get a second call to attemptDecryption, which sees
// that _decrypting is true so sets _retryDecryption // that _decryptionPromise is set so sets
// * A: we continue below, clear _decrypting, and never do // _retryDecryption
// the retry. // * A: we continue below, clear _decryptionPromise, and
// never do the retry.
// //
if (this._retryDecryption) { if (this._retryDecryption) {
// decryption error, but we have a retry queued. // decryption error, but we have a retry queued.
@@ -416,13 +429,13 @@ utils.extend(module.exports.MatrixEvent.prototype, {
// (and set res to a 'badEncryptedMessage'). Either way, we can now set the // (and set res to a 'badEncryptedMessage'). Either way, we can now set the
// cleartext of the event and raise Event.decrypted. // cleartext of the event and raise Event.decrypted.
// //
// make sure we clear '_decrypting' before sending the 'Event.decrypted' event, // make sure we clear '_decryptionPromise' before sending the 'Event.decrypted' event,
// otherwise the app will be confused to see `isBeingDecrypted` still set when // otherwise the app will be confused to see `isBeingDecrypted` still set when
// there isn't an `Event.decrypted` on the way. // there isn't an `Event.decrypted` on the way.
// //
// see also notes on _retryDecryption above. // see also notes on _retryDecryption above.
// //
this._decrypting = false; this._decryptionPromise = null;
this._retryDecryption = false; this._retryDecryption = false;
this._setClearData(res); this._setClearData(res);
return; return;