Skip to content

Commit 1712ccd

Browse files
committed
expose detailed queue params
1 parent 46b10cd commit 1712ccd

File tree

5 files changed

+62
-23
lines changed

5 files changed

+62
-23
lines changed

README.md

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,15 @@ For more information see the [Expo docs](https://docs.expo.dev/push-notification
188188

189189
### Throttling
190190

191-
Push providers usually throttle their APIs, so that sending too many pushes notifications within a short time may cause the API not accept any more requests. To address this, push sending can be throttled per provider by adding a `throttle` option to the respective push configuration. The option `maxPerSecond` defines the maximum number of pushes sent per second. If not throttle is configured, pushes are sent as quickly as possible.
191+
By default, pushes are sent as fast as possible. However, push providers usually throttle their APIs, so that sending too many pushes notifications within a short time may cause the API to reject requests. To address this, push sending can be throttled per provider by adding the `queue` option to the respective push configuration.
192192

193-
Example throttle configuration:
193+
| Parameter | Default | Optional | Description |
194+
|--------------------------|------------|----------|--------------------------------------------------------------------------------------------|
195+
| `queue.concurrency` | `Infinity` | Yes | The maximum number of pushes to process concurrently. |
196+
| `queue.intervalCapacity` | `Infinity` | Yes | The interval capacity, meaning the maximum number of tasks to process in a given interval. |
197+
| `queue.interval` | `0` | Yes | The interval in milliseconds for the interval capacity. |
198+
199+
Example configuration to throttle the queue to max. 1 push every 100ms, equivalent to max. 10 pushes per second:
194200

195201
```js
196202
const parseServerOptions = {
@@ -199,14 +205,39 @@ const parseServerOptions = {
199205
ios: {
200206
// ...
201207
queue: {
202-
throttle: { maxPerSecond: 100 }
203-
}
208+
concurrency: 1,
209+
intervalCapacity: 1,
210+
interval: 100,
211+
},
204212
}
205213
})
206214
}
207215
};
208216
```
209217

218+
Keep in mind that `concurrency: 1` means that pushes are sent in serial. For example, if sending a request to the push provider takes up to 500ms to complete, then the configuration above may be limited to only 2 pushes per second if every request takes 500ms. To address this, you can send pushes in parallel by setting the concurrency to a value greater than `1`, and increasing `intervalCapacity` and `interval` to fully utilize parallelism.
219+
220+
Example configuration sending pushes in parallel:
221+
222+
```js
223+
const parseServerOptions = {
224+
push: {
225+
adapter: new ParsePushAdapter({
226+
ios: {
227+
// ...
228+
queue: {
229+
concurrency: 5,
230+
intervalCapacity: 5,
231+
interval: 500,
232+
},
233+
}
234+
})
235+
}
236+
};
237+
```
238+
239+
In the example above, pushes will be sent in bursts of 5 at once, with max. 10 pushes within 1s. On a timeline that means at `t=0ms`, 5 pushes will be sent in parallel. If sending the pushes take less than 500ms, then `intervalCapacity` will still limit to 5 pushes within the first 500ms. At `t=500ms` the second interval begins and another max. 5 pushes are sent in parallel. That effectively means a throughput of up to 10 pushes per second.
240+
210241
### Push Options
211242

212243
Each push request may specify the following options for handling in the queue.

spec/ParsePushAdapter.spec.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,9 @@ describe('ParsePushAdapter', () => {
650650
senderId: 'id',
651651
apiKey: 'key',
652652
queue: {
653-
throttle: { maxPerSecond: 1 }
653+
concurrency: 1,
654+
intervalCapacity: 1,
655+
interval: 1_000,
654656
},
655657
},
656658
};
@@ -676,7 +678,9 @@ describe('ParsePushAdapter', () => {
676678
senderId: 'id',
677679
apiKey: 'key',
678680
queue: {
679-
throttle: { maxPerSecond: 1 }
681+
concurrency: 1,
682+
intervalCapacity: 1,
683+
interval: 1_000,
680684
},
681685
},
682686
};
@@ -699,7 +703,9 @@ describe('ParsePushAdapter', () => {
699703
senderId: 'id',
700704
apiKey: 'key',
701705
queue: {
702-
throttle: { maxPerSecond: 1 }
706+
concurrency: 1,
707+
intervalCapacity: 1,
708+
interval: 1_000,
703709
},
704710
},
705711
};

spec/ThrottleQueue.spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { wait } from './helper.js';
33

44
describe('ThrottleQueue', () => {
55
it('processes items respecting rate limit', async () => {
6-
const q = new ThrottleQueue(1);
6+
const q = new ThrottleQueue({ concurrency: 1, intervalCap: 1, interval: 1_000 });
77
const times = [];
88
const p1 = q.enqueue({ task: () => { times.push(Date.now()) } });
99
const p2 = q.enqueue({ task: () => { times.push(Date.now()) } });
@@ -14,7 +14,7 @@ describe('ThrottleQueue', () => {
1414
});
1515

1616
it('drops expired items', async () => {
17-
const q = new ThrottleQueue(1);
17+
const q = new ThrottleQueue({ concurrency: 1, intervalCap: 1, interval: 1_000 });
1818
const results = [];
1919
const p1 = q.enqueue({ task: () => wait(1_200) });
2020
const p2 = q.enqueue({ task: () => { throw new Error('should not run'); }, ttl: 1 });
@@ -24,7 +24,7 @@ describe('ThrottleQueue', () => {
2424
});
2525

2626
it('processes higher priority tasks first', async () => {
27-
const q = new ThrottleQueue(1);
27+
const q = new ThrottleQueue({ concurrency: 1, intervalCap: 1, interval: 1_000 });
2828
const results = [];
2929

3030
// Block queue with task so that the queue scheduler doesn't start processing enqueued items

src/ParsePushAdapter.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ export default class ParsePushAdapter {
5757
}
5858
break;
5959
}
60-
if (pushConfig[pushType].throttle) {
61-
const rate = pushConfig[pushType].throttle.maxPerSecond;
62-
this.queues[pushType] = new ThrottleQueue(rate);
60+
const queue = pushConfig[pushType].queue;
61+
if (queue) {
62+
const { concurrency, intervalCapacity, interval } = queue || {};
63+
this.queues[pushType] = new ThrottleQueue({ concurrency, intervalCap: intervalCapacity, interval });
6364
}
6465
}
6566
}

src/ThrottleQueue.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ import PQueue from 'p-queue';
33
export default class ThrottleQueue {
44

55
/**
6-
* Creates an instance of ThrottleQueue.
6+
* Creates an instance of ThrottleQueue. If no parameters are provided, then the queue will have no
7+
* throttling and will process tasks as fast as possible.
78
*
8-
* @param {number} [maxPerSecond=Infinity] The maximum number of tasks to process per second.
9-
* Optional, defaults to Infinity (no limit).
9+
* @param {Object} [options] The options.
10+
* @param {number} [options.concurrency=Infinity] The maximum number of tasks to process concurrently.
11+
* Optional, defaults to `Infinity`, meaning no limit on concurrency.
12+
* @param {number} [options.intervalCap=Infinity] The interval capacity, meaning the maximum number of
13+
* tasks to process in a given interval. Optional, defaults to `Infinity`, meaning no interval limit.
14+
* @param {number} [options.interval=0] The interval in milliseconds for the interval capacity.
15+
* Optional, defaults to `0`, meaning no interval limit.
1016
*/
11-
constructor(maxPerSecond = Infinity) {
12-
if (maxPerSecond === Infinity) {
13-
this.queue = new PQueue({ concurrency: Infinity });
14-
} else {
15-
const interval = Math.ceil(1000 / maxPerSecond);
16-
this.queue = new PQueue({ concurrency: 1, intervalCap: 1, interval });
17-
}
17+
constructor({ concurrency = Infinity, intervalCap = Infinity, interval = 0 } = {}) {
18+
this.queue = new PQueue({ concurrency, intervalCap, interval });
1819
}
1920

2021
/**

0 commit comments

Comments
 (0)