Skip to content

Commit e2169cf

Browse files
authored
Revert "feat(js-sdk): circuit breaker for usage reporting (#7259)" (#7278)
1 parent 6113f49 commit e2169cf

File tree

10 files changed

+50
-318
lines changed

10 files changed

+50
-318
lines changed

.changeset/beige-teams-spend.md

Lines changed: 0 additions & 38 deletions
This file was deleted.

packages/libraries/core/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,11 @@
4545
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
4646
},
4747
"dependencies": {
48-
"@graphql-hive/signal": "^2.0.0",
4948
"@graphql-tools/utils": "^10.0.0",
5049
"@whatwg-node/fetch": "^0.10.6",
5150
"async-retry": "^1.3.3",
5251
"js-md5": "0.8.3",
5352
"lodash.sortby": "^4.7.0",
54-
"opossum": "^9.0.0",
5553
"tiny-lru": "^8.0.2"
5654
},
5755
"devDependencies": {
@@ -60,7 +58,6 @@
6058
"@types/async-retry": "1.4.8",
6159
"@types/js-md5": "0.8.0",
6260
"@types/lodash.sortby": "4.7.9",
63-
"@types/opossum": "8.1.9",
6461
"graphql": "16.9.0",
6562
"nock": "14.0.10",
6663
"tslib": "2.8.1",

packages/libraries/core/playground/agent-circuit-breaker.ts

Lines changed: 0 additions & 52 deletions
This file was deleted.

packages/libraries/core/src/client/agent.ts

Lines changed: 21 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,10 @@
1-
import { fetch as defaultFetch } from '@whatwg-node/fetch';
21
import { version } from '../version.js';
32
import { http } from './http-client.js';
43
import type { Logger } from './types.js';
5-
import { CircuitBreakerInterface, createHiveLogger, loadCircuitBreaker } from './utils.js';
4+
import { createHiveLogger } from './utils.js';
65

76
type ReadOnlyResponse = Pick<Response, 'status' | 'text' | 'json' | 'statusText'>;
87

9-
export type AgentCircuitBreakerConfiguration = {
10-
/**
11-
* Percentage after what the circuit breaker should kick in.
12-
* Default: 50
13-
*/
14-
errorThresholdPercentage: number;
15-
/**
16-
* Count of requests before starting evaluating.
17-
* Default: 5
18-
*/
19-
volumeThreshold: number;
20-
/**
21-
* After what time the circuit breaker is attempting to retry sending requests in milliseconds
22-
* Default: 30_000
23-
*/
24-
resetTimeout: number;
25-
};
26-
27-
const defaultCircuitBreakerConfiguration: AgentCircuitBreakerConfiguration = {
28-
errorThresholdPercentage: 50,
29-
volumeThreshold: 10,
30-
resetTimeout: 30_000,
31-
};
32-
338
export interface AgentOptions {
349
enabled?: boolean;
3510
name?: string;
@@ -74,14 +49,7 @@ export interface AgentOptions {
7449
* WHATWG Compatible fetch implementation
7550
* used by the agent to send reports
7651
*/
77-
fetch?: typeof defaultFetch;
78-
/**
79-
* Circuit Breaker Configuration.
80-
* true -> Use default configuration
81-
* false -> Disable
82-
* object -> use custom configuration see {AgentCircuitBreakerConfiguration}
83-
*/
84-
circuitBreaker?: boolean | AgentCircuitBreakerConfiguration;
52+
fetch?: typeof fetch;
8553
}
8654

8755
export function createAgent<TEvent>(
@@ -100,9 +68,7 @@ export function createAgent<TEvent>(
10068
headers?(): Record<string, string>;
10169
},
10270
) {
103-
const options: Required<Omit<AgentOptions, 'fetch' | 'circuitBreaker' | 'logger' | 'debug'>> & {
104-
circuitBreaker: null | AgentCircuitBreakerConfiguration;
105-
} = {
71+
const options: Required<Omit<AgentOptions, 'fetch' | 'debug' | 'logger'>> = {
10672
timeout: 30_000,
10773
enabled: true,
10874
minTimeout: 200,
@@ -112,18 +78,9 @@ export function createAgent<TEvent>(
11278
name: 'hive-client',
11379
version,
11480
...pluginOptions,
115-
circuitBreaker:
116-
pluginOptions.circuitBreaker == null || pluginOptions.circuitBreaker === true
117-
? defaultCircuitBreakerConfiguration
118-
: pluginOptions.circuitBreaker === false
119-
? null
120-
: pluginOptions.circuitBreaker,
12181
};
122-
123-
const logger = createHiveLogger(pluginOptions.logger ?? console, '[agent]');
124-
82+
const logger = createHiveLogger(pluginOptions.logger ?? console, '[agent]', pluginOptions.debug);
12583
const enabled = options.enabled !== false;
126-
12784
let timeoutID: ReturnType<typeof setTimeout> | null = null;
12885

12986
function schedule() {
@@ -174,27 +131,6 @@ export function createAgent<TEvent>(
174131
return send({ throwOnError: true, skipSchedule: true });
175132
}
176133

177-
async function sendHTTPCall(buffer: string | Buffer<ArrayBufferLike>): Promise<Response> {
178-
const signal = breaker.getSignal();
179-
return await http.post(options.endpoint, buffer, {
180-
headers: {
181-
accept: 'application/json',
182-
'content-type': 'application/json',
183-
Authorization: `Bearer ${options.token}`,
184-
'User-Agent': `${options.name}/${options.version}`,
185-
...headers(),
186-
},
187-
timeout: options.timeout,
188-
retry: {
189-
retries: options.maxRetries,
190-
factor: 2,
191-
},
192-
logger,
193-
fetchImplementation: pluginOptions.fetch,
194-
signal,
195-
});
196-
}
197-
198134
async function send(sendOptions?: {
199135
throwOnError?: boolean;
200136
skipSchedule: boolean;
@@ -212,7 +148,23 @@ export function createAgent<TEvent>(
212148
data.clear();
213149

214150
logger.debug(`Sending report (queue ${dataToSend})`);
215-
const response = sendFromBreaker(buffer)
151+
const response = await http
152+
.post(options.endpoint, buffer, {
153+
headers: {
154+
accept: 'application/json',
155+
'content-type': 'application/json',
156+
Authorization: `Bearer ${options.token}`,
157+
'User-Agent': `${options.name}/${options.version}`,
158+
...headers(),
159+
},
160+
timeout: options.timeout,
161+
retry: {
162+
retries: options.maxRetries,
163+
factor: 2,
164+
},
165+
logger,
166+
fetchImplementation: pluginOptions.fetch,
167+
})
216168
.then(res => {
217169
logger.debug(`Report sent!`);
218170
return res;
@@ -251,74 +203,6 @@ export function createAgent<TEvent>(
251203
});
252204
}
253205

254-
let breaker: CircuitBreakerInterface<
255-
Parameters<typeof sendHTTPCall>,
256-
ReturnType<typeof sendHTTPCall>
257-
>;
258-
let loadCircuitBreakerPromise: Promise<void> | null = null;
259-
const breakerLogger = createHiveLogger(logger, '[circuit breaker]');
260-
261-
function noopBreaker(): typeof breaker {
262-
return {
263-
getSignal() {
264-
return undefined;
265-
},
266-
fire: sendHTTPCall,
267-
};
268-
}
269-
270-
if (options.circuitBreaker) {
271-
/**
272-
* We support Cloudflare, which does not has the `events` module.
273-
* So we lazy load opossum which has `events` as a dependency.
274-
*/
275-
breakerLogger.info('initialize circuit breaker');
276-
loadCircuitBreakerPromise = loadCircuitBreaker(
277-
CircuitBreaker => {
278-
breakerLogger.info('started');
279-
const realBreaker = new CircuitBreaker(sendHTTPCall, {
280-
...options.circuitBreaker,
281-
timeout: false,
282-
autoRenewAbortController: true,
283-
});
284-
285-
realBreaker.on('open', () =>
286-
breakerLogger.error('circuit opened - backend seems unreachable.'),
287-
);
288-
realBreaker.on('halfOpen', () =>
289-
breakerLogger.info('circuit half open - testing backend connectivity'),
290-
);
291-
realBreaker.on('close', () => breakerLogger.info('circuit closed - backend recovered '));
292-
293-
// @ts-expect-error missing definition in typedefs for `opposum`
294-
breaker = realBreaker;
295-
},
296-
() => {
297-
breakerLogger.info('circuit breaker not supported on platform');
298-
breaker = noopBreaker();
299-
},
300-
);
301-
} else {
302-
breaker = noopBreaker();
303-
}
304-
305-
async function sendFromBreaker(...args: Parameters<typeof breaker.fire>) {
306-
if (!breaker) {
307-
await loadCircuitBreakerPromise;
308-
}
309-
310-
try {
311-
return await breaker.fire(...args);
312-
} catch (err: unknown) {
313-
if (err instanceof Error && 'code' in err && err.code === 'EOPENBREAKER') {
314-
breakerLogger.info('circuit open - sending report skipped');
315-
return null;
316-
}
317-
318-
throw err;
319-
}
320-
}
321-
322206
return {
323207
capture,
324208
sendImmediately,

packages/libraries/core/src/client/http-client.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import asyncRetry from 'async-retry';
2-
import { abortSignalAny } from '@graphql-hive/signal';
32
import { crypto, fetch, URL } from '@whatwg-node/fetch';
43
import { Logger } from './types';
54

@@ -22,8 +21,6 @@ interface SharedConfig {
2221
* @default {response => response.ok}
2322
**/
2423
isRequestOk?: ResponseAssertFunction;
25-
/** Optional abort signal */
26-
signal?: AbortSignal;
2724
}
2825

2926
/**
@@ -81,8 +78,6 @@ export async function makeFetchCall(
8178
* @default {response => response.ok}
8279
**/
8380
isRequestOk?: ResponseAssertFunction;
84-
/** Optional abort signal */
85-
signal?: AbortSignal;
8681
},
8782
): Promise<Response> {
8883
const logger = config.logger;
@@ -92,9 +87,6 @@ export async function makeFetchCall(
9287
let maxTimeout = 2000;
9388
let factor = 1.2;
9489

95-
const actionHeader =
96-
config.method === 'POST' ? { 'x-client-action-id': crypto.randomUUID() } : undefined;
97-
9890
if (config.retry !== false) {
9991
retries = config.retry?.retries ?? 5;
10092
minTimeout = config.retry?.minTimeout ?? 200;
@@ -113,15 +105,13 @@ export async function makeFetchCall(
113105
);
114106

115107
const getDuration = measureTime();
116-
const timeoutSignal = AbortSignal.timeout(config.timeout ?? 20_000);
117-
const signal = config.signal ? abortSignalAny([config.signal, timeoutSignal]) : timeoutSignal;
108+
const signal = AbortSignal.timeout(config.timeout ?? 20_000);
118109

119110
const response = await (config.fetchImplementation ?? fetch)(endpoint, {
120111
method: config.method,
121112
body: config.body,
122113
headers: {
123114
'x-request-id': requestId,
124-
...actionHeader,
125115
...config.headers,
126116
},
127117
signal,
@@ -156,12 +146,6 @@ export async function makeFetchCall(
156146
throw new Error(`Unexpected HTTP error. (x-request-id=${requestId})`, { cause: error });
157147
});
158148

159-
if (config.signal?.aborted === true) {
160-
const error = config.signal.reason ?? new Error('Request aborted externally.');
161-
bail(error);
162-
throw error;
163-
}
164-
165149
if (isRequestOk(response)) {
166150
logger?.debug?.(
167151
`${config.method} ${endpoint} (x-request-id=${requestId}) succeeded with status ${response.status} ${getDuration()}.`,

0 commit comments

Comments
 (0)