Skip to content

Commit d032b06

Browse files
authored
ref(utils): refactor promisebuffer (#4323)
* ref(utils): refactor promisebuffer * ref(utils): remove length and isReady from public api * ref(promisebuffer): remove _buffer * fix(lint): run linter * fix(promisebuffer): add back the promise buffer
1 parent 477aec2 commit d032b06

File tree

5 files changed

+67
-50
lines changed

5 files changed

+67
-50
lines changed

packages/browser/src/transports/base.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
eventStatusFromHttpCode,
1818
getGlobalObject,
1919
logger,
20+
makePromiseBuffer,
2021
parseRetryAfterHeader,
2122
PromiseBuffer,
2223
SentryError,
@@ -42,7 +43,7 @@ export abstract class BaseTransport implements Transport {
4243
protected readonly _api: APIDetails;
4344

4445
/** A simple buffer holding all requests. */
45-
protected readonly _buffer: PromiseBuffer<SentryResponse> = new PromiseBuffer(30);
46+
protected readonly _buffer: PromiseBuffer<SentryResponse> = makePromiseBuffer(30);
4647

4748
/** Locks transport after receiving rate limits in a response */
4849
protected readonly _rateLimits: Record<string, Date> = {};

packages/core/test/mocks/transport.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Event, Response, Transport } from '@sentry/types';
2-
import { PromiseBuffer, SyncPromise } from '@sentry/utils';
2+
import { makePromiseBuffer, PromiseBuffer, SyncPromise } from '@sentry/utils';
33

44
async function sleep(delay: number): Promise<void> {
55
return new SyncPromise(resolve => setTimeout(resolve, delay));
@@ -11,7 +11,7 @@ export class FakeTransport implements Transport {
1111
public delay: number = 2000;
1212

1313
/** A simple buffer holding all requests. */
14-
protected readonly _buffer: PromiseBuffer<Response> = new PromiseBuffer(9999);
14+
protected readonly _buffer: PromiseBuffer<Response> = makePromiseBuffer(9999);
1515

1616
public sendEvent(_event: Event): PromiseLike<Response> {
1717
this.sendCalled += 1;

packages/node/src/transports/base/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ import {
1010
Transport,
1111
TransportOptions,
1212
} from '@sentry/types';
13-
import { eventStatusFromHttpCode, logger, parseRetryAfterHeader, PromiseBuffer, SentryError } from '@sentry/utils';
13+
import {
14+
eventStatusFromHttpCode,
15+
logger,
16+
makePromiseBuffer,
17+
parseRetryAfterHeader,
18+
PromiseBuffer,
19+
SentryError,
20+
} from '@sentry/utils';
1421
import * as fs from 'fs';
1522
import * as http from 'http';
1623
import * as https from 'https';
@@ -43,7 +50,7 @@ export abstract class BaseTransport implements Transport {
4350
protected _api: APIDetails;
4451

4552
/** A simple buffer holding all requests. */
46-
protected readonly _buffer: PromiseBuffer<Response> = new PromiseBuffer(30);
53+
protected readonly _buffer: PromiseBuffer<Response> = makePromiseBuffer(30);
4754

4855
/** Locks transport after receiving rate limits in a response */
4956
protected readonly _rateLimits: Record<string, Date> = {};

packages/utils/src/promisebuffer.ts

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,36 @@ function allPromises<U = unknown>(collection: Array<U | PromiseLike<U>>): Promis
2222
});
2323
}
2424

25-
/** A simple queue that holds promises. */
26-
export class PromiseBuffer<T> {
27-
/** Internal set of queued Promises */
28-
private readonly _buffer: Array<PromiseLike<T>> = [];
25+
export interface PromiseBuffer<T> {
26+
length(): number;
27+
add(taskProducer: () => PromiseLike<T>): PromiseLike<T>;
28+
remove(task: PromiseLike<T>): PromiseLike<T>;
29+
drain(timeout?: number): PromiseLike<boolean>;
30+
}
31+
32+
/**
33+
* Creates an new PromiseBuffer object with the specified limit
34+
* @param limit max number of promises that can be stored in the buffer
35+
*/
36+
export function makePromiseBuffer<T>(limit?: number): PromiseBuffer<T> {
37+
const buffer: Array<PromiseLike<T>> = [];
38+
39+
function length(): number {
40+
return buffer.length;
41+
}
2942

30-
public constructor(protected _limit?: number) {}
43+
function isReady(): boolean {
44+
return limit === undefined || buffer.length < limit;
45+
}
3146

3247
/**
33-
* Says if the buffer is ready to take more requests
48+
* Remove a promise from the queue.
49+
*
50+
* @param task Can be any PromiseLike<T>
51+
* @returns Removed promise.
3452
*/
35-
public isReady(): boolean {
36-
return this._limit === undefined || this.length() < this._limit;
53+
function remove(task: PromiseLike<T>): PromiseLike<T> {
54+
return buffer.splice(buffer.indexOf(task), 1)[0];
3755
}
3856

3957
/**
@@ -46,47 +64,29 @@ export class PromiseBuffer<T> {
4664
* limit check.
4765
* @returns The original promise.
4866
*/
49-
public add(taskProducer: () => PromiseLike<T>): PromiseLike<T> {
50-
if (!this.isReady()) {
67+
function add(taskProducer: () => PromiseLike<T>): PromiseLike<T> {
68+
if (!isReady()) {
5169
return SyncPromise.reject(new SentryError('Not adding Promise due to buffer limit reached.'));
5270
}
5371

5472
// start the task and add its promise to the queue
5573
const task = taskProducer();
56-
if (this._buffer.indexOf(task) === -1) {
57-
this._buffer.push(task);
74+
if (buffer.indexOf(task) === -1) {
75+
buffer.push(task);
5876
}
5977
void task
60-
.then(() => this.remove(task))
78+
.then(() => remove(task))
6179
// Use `then(null, rejectionHandler)` rather than `catch(rejectionHandler)` so that we can use `PromiseLike`
6280
// rather than `Promise`. `PromiseLike` doesn't have a `.catch` method, making its polyfill smaller. (ES5 didn't
6381
// have promises, so TS has to polyfill when down-compiling.)
6482
.then(null, () =>
65-
this.remove(task).then(null, () => {
66-
// We have to add another catch here because `this.remove()` starts a new promise chain.
83+
remove(task).then(null, () => {
84+
// We have to add another catch here because `remove()` starts a new promise chain.
6785
}),
6886
);
6987
return task;
7088
}
7189

72-
/**
73-
* Remove a promise from the queue.
74-
*
75-
* @param task Can be any PromiseLike<T>
76-
* @returns Removed promise.
77-
*/
78-
public remove(task: PromiseLike<T>): PromiseLike<T> {
79-
const removedTask = this._buffer.splice(this._buffer.indexOf(task), 1)[0];
80-
return removedTask;
81-
}
82-
83-
/**
84-
* This function returns the number of unresolved promises in the queue.
85-
*/
86-
public length(): number {
87-
return this._buffer.length;
88-
}
89-
9090
/**
9191
* Wait for all promises in the queue to resolve or for timeout to expire, whichever comes first.
9292
*
@@ -96,7 +96,7 @@ export class PromiseBuffer<T> {
9696
* @returns A promise which will resolve to `true` if the queue is already empty or drains before the timeout, and
9797
* `false` otherwise
9898
*/
99-
public drain(timeout?: number): PromiseLike<boolean> {
99+
function drain(timeout?: number): PromiseLike<boolean> {
100100
return new SyncPromise<boolean>(resolve => {
101101
// wait for `timeout` ms and then resolve to `false` (if not cancelled first)
102102
const capturedSetTimeout = setTimeout(() => {
@@ -106,10 +106,19 @@ export class PromiseBuffer<T> {
106106
}, timeout);
107107

108108
// if all promises resolve in time, cancel the timer and resolve to `true`
109-
void allPromises(this._buffer).then(() => {
109+
void allPromises(buffer).then(() => {
110110
clearTimeout(capturedSetTimeout);
111111
resolve(true);
112112
});
113113
});
114114
}
115+
116+
const promiseBuffer: PromiseBuffer<T> = {
117+
length,
118+
add,
119+
remove,
120+
drain,
121+
};
122+
123+
return promiseBuffer;
115124
}

packages/utils/test/promisebuffer.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import { PromiseBuffer } from '../src/promisebuffer';
1+
import { makePromiseBuffer } from '../src/promisebuffer';
22
import { SyncPromise } from '../src/syncpromise';
33

44
describe('PromiseBuffer', () => {
55
describe('add()', () => {
66
test('no limit', () => {
7-
const buffer = new PromiseBuffer();
7+
const buffer = makePromiseBuffer();
88
const p = jest.fn(() => new SyncPromise(resolve => setTimeout(resolve)));
99
void buffer.add(p);
1010
expect(buffer.length()).toEqual(1);
1111
});
1212

1313
test('with limit', () => {
14-
const buffer = new PromiseBuffer(1);
14+
const buffer = makePromiseBuffer(1);
1515
let task1;
1616
const producer1 = jest.fn(() => {
1717
task1 = new SyncPromise(resolve => setTimeout(resolve));
@@ -28,7 +28,7 @@ describe('PromiseBuffer', () => {
2828

2929
describe('drain()', () => {
3030
test('without timeout', async () => {
31-
const buffer = new PromiseBuffer();
31+
const buffer = makePromiseBuffer();
3232
for (let i = 0; i < 5; i++) {
3333
void buffer.add(() => new SyncPromise(resolve => setTimeout(resolve)));
3434
}
@@ -39,7 +39,7 @@ describe('PromiseBuffer', () => {
3939
});
4040

4141
test('with timeout', async () => {
42-
const buffer = new PromiseBuffer();
42+
const buffer = makePromiseBuffer();
4343
for (let i = 0; i < 5; i++) {
4444
void buffer.add(() => new SyncPromise(resolve => setTimeout(resolve, 100)));
4545
}
@@ -49,7 +49,7 @@ describe('PromiseBuffer', () => {
4949
});
5050

5151
test('on empty buffer', async () => {
52-
const buffer = new PromiseBuffer();
52+
const buffer = makePromiseBuffer();
5353
expect(buffer.length()).toEqual(0);
5454
const result = await buffer.drain();
5555
expect(result).toEqual(true);
@@ -58,7 +58,7 @@ describe('PromiseBuffer', () => {
5858
});
5959

6060
test('resolved promises should not show up in buffer length', async () => {
61-
const buffer = new PromiseBuffer();
61+
const buffer = makePromiseBuffer();
6262
const producer = () => new SyncPromise(resolve => setTimeout(resolve));
6363
const task = buffer.add(producer);
6464
expect(buffer.length()).toEqual(1);
@@ -67,7 +67,7 @@ describe('PromiseBuffer', () => {
6767
});
6868

6969
test('rejected promises should not show up in buffer length', async () => {
70-
const buffer = new PromiseBuffer();
70+
const buffer = makePromiseBuffer();
7171
const producer = () => new SyncPromise((_, reject) => setTimeout(reject));
7272
const task = buffer.add(producer);
7373
expect(buffer.length()).toEqual(1);
@@ -80,7 +80,7 @@ describe('PromiseBuffer', () => {
8080
});
8181

8282
test('resolved task should give an access to the return value', async () => {
83-
const buffer = new PromiseBuffer<string>();
83+
const buffer = makePromiseBuffer<string>();
8484
const producer = () => new SyncPromise<string>(resolve => setTimeout(() => resolve('test')));
8585
const task = buffer.add(producer);
8686
const result = await task;
@@ -89,7 +89,7 @@ describe('PromiseBuffer', () => {
8989

9090
test('rejected task should give an access to the return value', async () => {
9191
expect.assertions(1);
92-
const buffer = new PromiseBuffer<string>();
92+
const buffer = makePromiseBuffer<string>();
9393
const producer = () => new SyncPromise<string>((_, reject) => setTimeout(() => reject(new Error('whoops'))));
9494
const task = buffer.add(producer);
9595
try {

0 commit comments

Comments
 (0)