Skip to content

Commit 1d82966

Browse files
committed
merged timeout managers into one
1 parent c16f737 commit 1d82966

File tree

8 files changed

+65
-88
lines changed

8 files changed

+65
-88
lines changed

src/api/data-api-http-client.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ import { DataAPIResponseError, DataAPITimeout, mkRespErrorFromResponse, ObjectId
1717
import { logger } from '@/src/logger';
1818
import {
1919
MkTimeoutError,
20-
MultiCallTimeoutManager,
21-
SingleCallTimeoutManager,
2220
TimeoutManager,
2321
TimeoutOptions,
2422
} from '@/src/api/timeout-managers';
@@ -40,12 +38,12 @@ export class DataApiHttpClient extends HttpClient {
4038
public collection?: string;
4139
public namespace?: string;
4240

43-
public multiCallTimeoutManager(timeoutMs: number | undefined) {
44-
return mkTimeoutManager(MultiCallTimeoutManager, timeoutMs);
41+
public timeoutManager(timeoutMs: number | undefined) {
42+
return mkTimeoutManager(timeoutMs);
4543
}
4644

4745
public async executeCommand(command: Record<string, any>, options: ExecuteCommandOptions | undefined) {
48-
const timeoutManager = options?.timeoutManager ?? mkTimeoutManager(SingleCallTimeoutManager, options?.maxTimeMS);
46+
const timeoutManager = options?.timeoutManager ?? mkTimeoutManager(options?.maxTimeMS);
4947

5048
const response = await this._requestDataApi({
5149
url: this.baseUrl,
@@ -104,9 +102,9 @@ export class DataApiHttpClient extends HttpClient {
104102
}
105103
}
106104

107-
const mkTimeoutManager = (constructor: new (maxMs: number, mkTimeoutError: MkTimeoutError) => TimeoutManager, maxMs: number | undefined) => {
105+
const mkTimeoutManager = (maxMs: number | undefined) => {
108106
const timeout = maxMs ?? DEFAULT_TIMEOUT;
109-
return new constructor(timeout, mkTimeoutErrorMaker(timeout));
107+
return new TimeoutManager(timeout, mkTimeoutErrorMaker(timeout));
110108
}
111109

112110
const mkTimeoutErrorMaker = (timeout: number): MkTimeoutError => {

src/api/devops-api-http-client.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ import { HTTP1AuthHeaderFactories, HTTP1Strategy } from '@/src/api/http1';
1919
import { DevopsApiResponseError, DevopsApiTimeout, DevopsUnexpectedStateError } from '@/src/devops/errors';
2020
import { AdminBlockingOptions, PollBlockingOptions } from '@/src/devops/types';
2121
import {
22-
MkTimeoutError,
23-
MultiCallTimeoutManager,
24-
SingleCallTimeoutManager,
25-
TimeoutManager,
22+
MkTimeoutError, TimeoutManager,
2623
TimeoutOptions,
2724
} from '@/src/api/timeout-managers';
2825
import { HttpMethods } from '@/src/api/constants';
@@ -50,7 +47,7 @@ export class DevopsApiHttpClient extends HttpClient {
5047

5148
public async request(info: DevopsApiRequestInfo, options: TimeoutOptions | undefined): Promise<AxiosResponse> {
5249
try {
53-
const timeoutManager = options?.timeoutManager ?? mkTimeoutManager(SingleCallTimeoutManager, options?.maxTimeMS);
50+
const timeoutManager = options?.timeoutManager ?? mkTimeoutManager(options?.maxTimeMS);
5451
const url = this.baseUrl + info.path;
5552

5653
return await this._request({
@@ -69,7 +66,7 @@ export class DevopsApiHttpClient extends HttpClient {
6966
}
7067

7168
public async requestLongRunning(req: DevopsApiRequestInfo, info: LongRunningRequestInfo): Promise<AxiosResponse> {
72-
const timeoutManager = mkTimeoutManager(MultiCallTimeoutManager, info.options?.maxTimeMS);
69+
const timeoutManager = mkTimeoutManager(info.options?.maxTimeMS);
7370
const resp = await this.request(req, { timeoutManager });
7471

7572
const id = (typeof info.id === 'function')
@@ -107,9 +104,9 @@ export class DevopsApiHttpClient extends HttpClient {
107104
}
108105
}
109106

110-
const mkTimeoutManager = (constructor: new (maxMs: number, mkTimeoutError: MkTimeoutError) => TimeoutManager, maxMs: number | undefined) => {
107+
const mkTimeoutManager = (maxMs: number | undefined) => {
111108
const timeout = maxMs ?? 0;
112-
return new constructor(timeout, mkTimeoutErrorMaker(timeout));
109+
return new TimeoutManager(timeout, mkTimeoutErrorMaker(timeout));
113110
}
114111

115112
const mkTimeoutErrorMaker = (timeout: number): MkTimeoutError => {

src/api/timeout-managers.ts

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -43,50 +43,14 @@ export type TimeoutOptions = {
4343
*/
4444
export type MkTimeoutError = (ctx: InternalHTTPRequestInfo) => Error;
4545

46-
/**
47-
* Represents a timeout manager, which is responsible for tracking the remaining time before a timeout occurs.
48-
*
49-
* See {@link SingleCallTimeoutManager} and {@link MultiCallTimeoutManager} for concrete implementations.
50-
*
51-
* @internal
52-
*/
53-
export interface TimeoutManager {
54-
/**
55-
* The remaining time before a timeout occurs, in milliseconds. May return `Infinity`, which
56-
* the underlying http request strategies should interpret as no timeout.
57-
*
58-
* However, the implementing TimeoutManager classes should accept `0` to also indicate no timeout,
59-
* just translating it to `Infinity` for the purposes of the `msRemaining` getter, so it's easier to
60-
* reason about.
61-
*/
62-
msRemaining: number,
63-
/**
64-
* A function that creates a timeout error for the given request context ({@link InternalHTTPRequestInfo}).
65-
*/
66-
mkTimeoutError: MkTimeoutError,
67-
}
68-
69-
/**
70-
* A basic timeout manager that only holds a fixed timeout value, intended for, of course, single-request operations.
71-
*
72-
* @internal
73-
*/
74-
export class SingleCallTimeoutManager implements TimeoutManager {
75-
public readonly msRemaining: number;
76-
77-
constructor(maxMs: number, readonly mkTimeoutError: MkTimeoutError) {
78-
this.msRemaining = maxMs || Infinity;
79-
}
80-
}
81-
8246
/**
8347
* A more complex timeout manager that tracks the remaining time for multiple calls, starting from the first call.
8448
* This is useful for scenarios where multiple calls are made in sequence, and the timeout should be shared among them,
8549
* e.g. {@link Collection.insertMany}.
8650
*
8751
* @internal
8852
*/
89-
export class MultiCallTimeoutManager implements TimeoutManager {
53+
export class TimeoutManager {
9054
private _deadline!: number;
9155
private _started: boolean;
9256

@@ -98,7 +62,9 @@ export class MultiCallTimeoutManager implements TimeoutManager {
9862
get msRemaining() {
9963
if (!this._started) {
10064
this._started = true;
101-
this._deadline = Date.now() + this._deadline;
65+
const maxMs = this._deadline;
66+
this._deadline = Date.now() + maxMs;
67+
return maxMs
10268
}
10369
return this._deadline - Date.now();
10470
}

src/data-api/collection.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ export class Collection<Schema extends SomeDoc = SomeDoc> {
297297
}
298298
}
299299

300-
const timeoutManager = this._httpClient.multiCallTimeoutManager(options?.maxTimeMS);
300+
const timeoutManager = this._httpClient.timeoutManager(options?.maxTimeMS);
301301

302302
const insertedIds = (options?.ordered)
303303
? await insertManyOrdered<Schema>(this._httpClient, documents, chunkSize, timeoutManager)
@@ -449,7 +449,7 @@ export class Collection<Schema extends SomeDoc = SomeDoc> {
449449
},
450450
};
451451

452-
const timeoutManager = this._httpClient.multiCallTimeoutManager(options?.maxTimeMS);
452+
const timeoutManager = this._httpClient.timeoutManager(options?.maxTimeMS);
453453

454454
const commonResult = {
455455
modifiedCount: 0,
@@ -660,7 +660,7 @@ export class Collection<Schema extends SomeDoc = SomeDoc> {
660660
deleteMany: { filter },
661661
};
662662

663-
const timeoutManager = this._httpClient.multiCallTimeoutManager(options?.maxTimeMS);
663+
const timeoutManager = this._httpClient.timeoutManager(options?.maxTimeMS);
664664

665665
let resp;
666666
let numDeleted = 0;
@@ -1398,7 +1398,7 @@ export class Collection<Schema extends SomeDoc = SomeDoc> {
13981398
* @throws BulkWriteError - If the operation fails
13991399
*/
14001400
public async bulkWrite(operations: AnyBulkWriteOperation<Schema>[], options?: BulkWriteOptions): Promise<BulkWriteResult> {
1401-
const timeoutManager = this._httpClient.multiCallTimeoutManager(options?.maxTimeMS)
1401+
const timeoutManager = this._httpClient.timeoutManager(options?.maxTimeMS)
14021402

14031403
return (options?.ordered)
14041404
? await bulkWriteOrdered(this._httpClient, operations, timeoutManager)

src/data-api/db.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ export class Db implements Disposable {
311311
},
312312
};
313313

314-
const timeoutManager = this._httpClient.multiCallTimeoutManager(options?.maxTimeMS);
314+
const timeoutManager = this._httpClient.timeoutManager(options?.maxTimeMS);
315315
const namespace = options?.namespace ?? this.namespace;
316316

317317
if (options?.checkExists !== false) {

tests/integration/api/devops-http-client.test.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,12 @@ describe('integration.api.devops-http-client', () => {
2626
httpClient = db.admin()['_httpClient'];
2727
});
2828

29-
describe('request tests', () => {
30-
it('should timeout properly', async () => {
31-
await assert.rejects(async () => {
32-
await httpClient.request({
33-
method: HttpMethods.Get,
34-
path: '/databases',
35-
}, { maxTimeMS: 1 });
36-
}, DevopsApiTimeout);
37-
});
29+
it('should timeout properly', async () => {
30+
await assert.rejects(async () => {
31+
await httpClient.request({
32+
method: HttpMethods.Get,
33+
path: '/databases',
34+
}, { maxTimeMS: 1 });
35+
}, DevopsApiTimeout);
3836
});
3937
});

tests/integration/devops/lifecycle.test.ts

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import { assertTestsEnabled, initTestObjects } from '@/tests/fixtures';
1717
import { DataApiClient } from '@/src/client';
1818
import assert from 'assert';
1919
import { DevopsApiResponseError } from '@/src/devops';
20-
import { SingleCallTimeoutManager } from '@/src/api/timeout-managers';
2120
import { DEFAULT_NAMESPACE, DEFAULT_TIMEOUT } from '@/src/api';
21+
import { TimeoutManager } from '@/src/api/timeout-managers';
2222

2323
describe('integration.devops.lifecycle', async () => {
2424
let client: DataApiClient;
@@ -28,23 +28,11 @@ describe('integration.devops.lifecycle', async () => {
2828

2929
[client] = await initTestObjects(this);
3030

31-
// const dbs = await client.admin().listDatabases();
32-
//
33-
// if (dbs.some(db => db.info.name === 'astra-test-db')) {
34-
// throw new Error('Database \'astra-test-db\' already exists, drop it to proceed w/ lifecycle test');
35-
// }
36-
});
37-
38-
it('sdfsadfsda', async () => {
39-
const admin = client.admin();
31+
const dbs = await client.admin().listDatabases();
4032

41-
const resp = await admin.createDatabase({
42-
name: 'astra-test-db',
43-
cloudProvider: 'GCP',
44-
region: 'us-east1',
45-
});
46-
47-
console.log(resp.id);
33+
if (dbs.some(db => db.info.name === 'astra-test-db')) {
34+
throw new Error('Database \'astra-test-db\' already exists, drop it to proceed w/ lifecycle test');
35+
}
4836
});
4937

5038
it('[admin] works', async () => {
@@ -103,7 +91,7 @@ describe('integration.devops.lifecycle', async () => {
10391
}
10492

10593
{
106-
await asyncDbAdmin['_httpClient']['_awaitStatus'](asyncDb.id, 'ACTIVE', ['INITIALIZING', 'PENDING'], {}, 10000, new SingleCallTimeoutManager(DEFAULT_TIMEOUT, () => new Error('Timeout')));
94+
await asyncDbAdmin['_httpClient']['_awaitStatus'](asyncDb.id, 'ACTIVE', ['INITIALIZING', 'PENDING'], {}, 10000, new TimeoutManager(DEFAULT_TIMEOUT, () => new Error('Timeout')));
10795
}
10896

10997
for (const [dbAdmin, db, dbType] of [[syncDbAdmin, syncDb, 'sync'], [asyncDbAdmin, asyncDb, 'async']] as const) {
@@ -146,7 +134,7 @@ describe('integration.devops.lifecycle', async () => {
146134

147135
{
148136
await syncDbAdmin.createNamespace('other_namespace');
149-
await asyncDbAdmin['_httpClient']['_awaitStatus'](asyncDb.id, 'ACTIVE', ['MAINTENANCE'], {}, 1000, new SingleCallTimeoutManager(DEFAULT_TIMEOUT, () => new Error('Timeout')));
137+
await asyncDbAdmin['_httpClient']['_awaitStatus'](asyncDb.id, 'ACTIVE', ['MAINTENANCE'], {}, 1000, new TimeoutManager(DEFAULT_TIMEOUT, () => new Error('Timeout')));
150138
}
151139

152140
for (const [dbAdmin, db, dbType] of [[syncDbAdmin, syncDb, 'sync'], [asyncDbAdmin, asyncDb, 'async']] as const) {
@@ -165,7 +153,7 @@ describe('integration.devops.lifecycle', async () => {
165153

166154
{
167155
await syncDbAdmin.dropNamespace('other_namespace', { blocking: true });
168-
await asyncDbAdmin['_httpClient']['_awaitStatus'](asyncDb.id, 'ACTIVE', ['MAINTENANCE'], {}, 1000, new SingleCallTimeoutManager(DEFAULT_TIMEOUT, () => new Error('Timeout')));
156+
await asyncDbAdmin['_httpClient']['_awaitStatus'](asyncDb.id, 'ACTIVE', ['MAINTENANCE'], {}, 1000, new TimeoutManager(DEFAULT_TIMEOUT, () => new Error('Timeout')));
169157
}
170158

171159
for (const [dbAdmin, db, dbType] of [[syncDbAdmin, syncDb, 'sync'], [asyncDbAdmin, asyncDb, 'async']] as const) {
@@ -186,7 +174,7 @@ describe('integration.devops.lifecycle', async () => {
186174

187175
{
188176
await admin.dropDatabase(syncDb);
189-
await asyncDbAdmin['_httpClient']['_awaitStatus'](asyncDb.id, 'TERMINATED', ['TERMINATING'], {}, 10000, new SingleCallTimeoutManager(DEFAULT_TIMEOUT, () => new Error('Timeout')));
177+
await asyncDbAdmin['_httpClient']['_awaitStatus'](asyncDb.id, 'TERMINATED', ['TERMINATING'], {}, 10000, new TimeoutManager(DEFAULT_TIMEOUT, () => new Error('Timeout')));
190178
}
191179

192180
for (const [dbAdmin, dbType] of [[syncDbAdmin, 'sync'], [asyncDbAdmin, 'async']] as const) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright DataStax, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
// noinspection DuplicatedCode
15+
16+
import assert from 'assert';
17+
import { TimeoutManager } from '@/src/api/timeout-managers';
18+
19+
describe('unit.api.timeout-manager', () => {
20+
it('works', async () => {
21+
const timeoutManager = new TimeoutManager(1000, () => new Error('timeout'));
22+
assert.strictEqual(timeoutManager.msRemaining, 1000);
23+
await new Promise((resolve) => setTimeout(resolve, 500));
24+
assert.ok(timeoutManager.msRemaining < 500);
25+
assert.ok(timeoutManager.msRemaining > 490);
26+
await new Promise((resolve) => setTimeout(resolve, 500));
27+
assert.ok(timeoutManager.msRemaining < 0);
28+
assert.ok(timeoutManager.msRemaining > -20);
29+
});
30+
});

0 commit comments

Comments
 (0)