Skip to content

Commit ea67d23

Browse files
authored
Improvements for sending push performance (#4122)
* Adds test for stalled pushStatus when audience is empty * fixup! Adds test for stalled pushStatus when audience is empty * Do not enqueue when count is 0, enforce deviceToken exists, stop badge ordering
1 parent cfd7bc0 commit ea67d23

File tree

5 files changed

+92
-18
lines changed

5 files changed

+92
-18
lines changed

spec/PushController.spec.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,72 @@ describe('PushController', () => {
845845
fail('should not fail');
846846
done();
847847
});
848+
});
848849

850+
it('should mark the _PushStatus as succeeded when audience has no deviceToken', (done) => {
851+
var auth = {
852+
isMaster: true
853+
}
854+
var pushAdapter = {
855+
send: function(body, installations) {
856+
const promises = installations.map((device) => {
857+
if (!device.deviceToken) {
858+
// Simulate error when device token is not set
859+
return Promise.reject();
860+
}
861+
return Promise.resolve({
862+
transmitted: true,
863+
device: device,
864+
})
865+
});
866+
867+
return Promise.all(promises);
868+
},
869+
getValidPushTypes: function() {
870+
return ["ios"];
871+
}
872+
}
873+
874+
var pushController = new PushController();
875+
const payload = {
876+
data: {
877+
alert: 'hello',
878+
},
879+
push_time: new Date().getTime() / 1000
880+
}
881+
882+
var installations = [];
883+
while(installations.length != 5) {
884+
const installation = new Parse.Object("_Installation");
885+
installation.set("installationId", "installation_" + installations.length);
886+
installation.set("badge", installations.length);
887+
installation.set("originalBadge", installations.length);
888+
installation.set("deviceType", "ios");
889+
installations.push(installation);
890+
}
891+
892+
reconfigureServer({
893+
push: { adapter: pushAdapter }
894+
}).then(() => {
895+
var config = new Config(Parse.applicationId);
896+
return Parse.Object.saveAll(installations).then(() => {
897+
return pushController.sendPush(payload, {}, config, auth)
898+
.then(() => { done.fail('should not success') })
899+
.catch(() => {})
900+
}).then(() => new Promise(resolve => setTimeout(resolve, 100)));
901+
}).then(() => {
902+
const query = new Parse.Query('_PushStatus');
903+
return query.find({useMasterKey: true}).then((results) => {
904+
expect(results.length).toBe(1);
905+
const pushStatus = results[0];
906+
expect(pushStatus.get('numSent')).toBe(0);
907+
expect(pushStatus.get('status')).toBe('failed');
908+
done();
909+
});
910+
}).catch((err) => {
911+
console.error(err);
912+
fail('should not fail');
913+
done();
914+
});
849915
});
850916
});

src/Controllers/PushController.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Parse } from 'parse/node';
2-
import deepcopy from 'deepcopy';
32
import RestQuery from '../RestQuery';
43
import RestWrite from '../RestWrite';
54
import { master } from '../Auth';
65
import { pushStatusHandler } from '../StatusHandler';
6+
import { applyDeviceTokenExists } from '../Push/utils';
77

88
export class PushController {
99

@@ -23,6 +23,7 @@ export class PushController {
2323
let badgeUpdate = () => {
2424
return Promise.resolve();
2525
}
26+
2627
if (body.data && body.data.badge) {
2728
const badge = body.data.badge;
2829
let restUpdate = {};
@@ -33,8 +34,9 @@ export class PushController {
3334
} else {
3435
throw "Invalid value for badge, expected number or 'Increment'";
3536
}
36-
const updateWhere = deepcopy(where);
3737

38+
// Force filtering on only valid device tokens
39+
const updateWhere = applyDeviceTokenExists(where);
3840
badgeUpdate = () => {
3941
// Build a real RestQuery so we can use it in RestWrite
4042
const restQuery = new RestQuery(config, master(config), '_Installation', updateWhere);

src/Push/PushQueue.js

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { ParseMessageQueue } from '../ParseMessageQueue';
2-
import rest from '../rest';
3-
import { isPushIncrementing } from './utils';
4-
import deepcopy from 'deepcopy';
1+
import { ParseMessageQueue } from '../ParseMessageQueue';
2+
import rest from '../rest';
3+
import { applyDeviceTokenExists } from './utils';
54

65
const PUSH_CHANNEL = 'parse-server-push';
76
const DEFAULT_BATCH_SIZE = 100;
@@ -25,21 +24,19 @@ export class PushQueue {
2524

2625
enqueue(body, where, config, auth, pushStatus) {
2726
const limit = this.batchSize;
28-
// Order by badge (because the payload is badge dependant)
29-
// and createdAt to fix the order
30-
const order = isPushIncrementing(body) ? 'badge,createdAt' : 'createdAt';
31-
where = deepcopy(where);
32-
if (!where.hasOwnProperty('deviceToken')) {
33-
where['deviceToken'] = {'$exists': true};
34-
}
27+
28+
where = applyDeviceTokenExists(where);
29+
30+
// Order by objectId so no impact on the DB
31+
const order = 'objectId';
3532
return Promise.resolve().then(() => {
3633
return rest.find(config,
3734
auth,
3835
'_Installation',
3936
where,
4037
{limit: 0, count: true});
4138
}).then(({results, count}) => {
42-
if (!results) {
39+
if (!results || count == 0) {
4340
return Promise.reject({error: 'PushController: no results in query'})
4441
}
4542
pushStatus.setRunning(count);

src/Push/PushWorker.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Config from '../Config';
66
import { PushAdapter } from '../Adapters/Push/PushAdapter';
77
import rest from '../rest';
88
import { pushStatusHandler } from '../StatusHandler';
9-
import { isPushIncrementing } from './utils';
9+
import * as utils from './utils';
1010
import { ParseMessageQueue } from '../ParseMessageQueue';
1111
import { PushQueue } from './PushQueue';
1212

@@ -51,7 +51,7 @@ export class PushWorker {
5151
run({ body, query, pushStatus, applicationId }: any): Promise<*> {
5252
const config = new Config(applicationId);
5353
const auth = master(config);
54-
const where = query.where;
54+
const where = utils.applyDeviceTokenExists(query.where);
5555
delete query.where;
5656
return rest.find(config, auth, '_Installation', where, query).then(({results}) => {
5757
if (results.length == 0) {
@@ -65,7 +65,7 @@ export class PushWorker {
6565

6666
sendToAdapter(body: any, installations: any[], pushStatus: any, config: Config): Promise<*> {
6767
pushStatus = pushStatusHandler(config, pushStatus.objectId);
68-
if (!isPushIncrementing(body)) {
68+
if (!utils.isPushIncrementing(body)) {
6969
return this.adapter.send(body, installations, pushStatus.objectId).then((results) => {
7070
return pushStatus.trackSent(results);
7171
});

src/Push/utils.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import Parse from 'parse/node';
1+
import Parse from 'parse/node';
2+
import deepcopy from 'deepcopy';
23

34
export function isPushIncrementing(body) {
45
return body.data &&
@@ -28,3 +29,11 @@ export function validatePushType(where = {}, validPushTypes = []) {
2829
}
2930
}
3031
}
32+
33+
export function applyDeviceTokenExists(where) {
34+
where = deepcopy(where);
35+
if (!where.hasOwnProperty('deviceToken')) {
36+
where['deviceToken'] = {'$exists': true};
37+
}
38+
return where;
39+
}

0 commit comments

Comments
 (0)