brass-runtime 1.19.1 → 1.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/{chunk-ID2WOCBA.mjs → chunk-5X3MPWTR.mjs} +272 -36
- package/dist/{chunk-U42YF6B2.js → chunk-6AGAZD32.js} +2 -2
- package/dist/{chunk-XJSWDU2S.js → chunk-BHX4MD35.js} +1 -1
- package/dist/{chunk-IHXF6NQG.js → chunk-HKLZJ5UK.js} +272 -36
- package/dist/{chunk-22HYIE56.mjs → chunk-IA6BDGXW.mjs} +2 -2
- package/dist/{chunk-EZ76UNHP.cjs → chunk-QAHW7S3Q.cjs} +6 -6
- package/dist/{chunk-67256TKF.cjs → chunk-TWEHIAUE.cjs} +13 -13
- package/dist/{chunk-LXNU3MRV.cjs → chunk-UOKXJQAI.cjs} +367 -131
- package/dist/{chunk-AITYRQX2.mjs → chunk-ZXXOXB3T.mjs} +1 -1
- package/dist/http/index.cjs +33 -33
- package/dist/http/index.js +1 -1
- package/dist/http/index.mjs +1 -1
- package/dist/http/testing.cjs +4 -4
- package/dist/http/testing.js +1 -1
- package/dist/http/testing.mjs +1 -1
- package/dist/observability/index.cjs +3 -3
- package/dist/observability/index.js +2 -2
- package/dist/observability/index.mjs +2 -2
- package/dist/perf/cli.cjs +18 -18
- package/dist/perf/cli.js +3 -3
- package/dist/perf/cli.mjs +3 -3
- package/dist/perf/index.cjs +5 -5
- package/dist/perf/index.js +3 -3
- package/dist/perf/index.mjs +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.18.3 - HTTP Pool/Timeout Fast Path
|
|
4
|
+
|
|
5
|
+
### Performance
|
|
6
|
+
|
|
7
|
+
- Connected the non-streaming HTTP wire client to the existing `runPoolTransport`
|
|
8
|
+
fast path whenever a request uses `pool`, `adaptiveLimiter`, or `timeoutMs`.
|
|
9
|
+
The uncontended pool path now uses synchronous `tryAcquireSync` before falling
|
|
10
|
+
back to queued async acquire, avoiding the generic `fromPromiseAbortable`
|
|
11
|
+
wrapper and extra promise/microtask boundaries on hot BFF/proxy paths.
|
|
12
|
+
- Improved local HTTP overhead benchmark results for the proxy effect transport
|
|
13
|
+
path with 30k calls, 5k warmup, concurrency 32:
|
|
14
|
+
- `default-proxy-effect-timeout`: p99 `1.915ms` -> `1.559ms`,
|
|
15
|
+
throughput `70.9k/s` -> `101.5k/s`.
|
|
16
|
+
- `default-proxy-effect-pool`: p99 `1.564ms` -> `1.276ms`,
|
|
17
|
+
throughput `79.2k/s` -> `115.7k/s`.
|
|
18
|
+
- `default-proxy-effect-timeout-pool`: p99 `2.014ms` -> `1.209ms`,
|
|
19
|
+
throughput `70.2k/s` -> `103.1k/s`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Preserved real host-request cancellation on the pool/timeout fast path by
|
|
24
|
+
passing transports a request-scoped `AbortController` signal. Promise
|
|
25
|
+
transports such as Axios can now observe aborts on cancellation and timeout
|
|
26
|
+
while still benefiting from the pool fast path.
|
|
27
|
+
- Added regression coverage ensuring uncontended pool transports run during
|
|
28
|
+
effect registration and that cancellation aborts the signal passed to the
|
|
29
|
+
transport.
|
|
30
|
+
|
|
31
|
+
### Validation
|
|
32
|
+
|
|
33
|
+
- `npm run test:types`
|
|
34
|
+
- `npm test -- src/http/__tests__`
|
|
35
|
+
- `BRASS_HTTP_OVERHEAD_CALLS=30000 BRASS_HTTP_OVERHEAD_WARMUP_CALLS=5000 BRASS_HTTP_OVERHEAD_CONCURRENCY=32 BRASS_HTTP_OVERHEAD_VARIANTS=default-proxy-effect-transport,default-proxy-effect-timeout,default-proxy-effect-pool,default-proxy-effect-timeout-pool npm run benchmark:http:overhead`
|
|
36
|
+
|
|
3
37
|
## 1.18.2 - Release Metadata Alignment
|
|
4
38
|
|
|
5
39
|
### Fixed
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
} from "./chunk-XRRC5BBM.mjs";
|
|
13
13
|
import {
|
|
14
14
|
fromPromiseAbortable,
|
|
15
|
+
recordAbortablePromiseFinish,
|
|
16
|
+
recordAbortablePromiseStart,
|
|
15
17
|
resolveWasmModule,
|
|
16
18
|
toPromise
|
|
17
19
|
} from "./chunk-BFNBU47C.mjs";
|
|
@@ -3182,6 +3184,275 @@ var noopSignal = (() => {
|
|
|
3182
3184
|
globalThis.__brassNoopSignal = s;
|
|
3183
3185
|
return s;
|
|
3184
3186
|
})();
|
|
3187
|
+
var PoolRequestState = class {
|
|
3188
|
+
constructor(req, url, transport, metrics, pool, adaptiveLimiter, timerWheel, timeoutMs, env, cb, previousSignal, controller, signal, label, startedAt) {
|
|
3189
|
+
this.req = req;
|
|
3190
|
+
this.url = url;
|
|
3191
|
+
this.transport = transport;
|
|
3192
|
+
this.metrics = metrics;
|
|
3193
|
+
this.pool = pool;
|
|
3194
|
+
this.adaptiveLimiter = adaptiveLimiter;
|
|
3195
|
+
this.timerWheel = timerWheel;
|
|
3196
|
+
this.timeoutMs = timeoutMs;
|
|
3197
|
+
this.env = env;
|
|
3198
|
+
this.cb = cb;
|
|
3199
|
+
this.previousSignal = previousSignal;
|
|
3200
|
+
this.controller = controller;
|
|
3201
|
+
this.signal = signal;
|
|
3202
|
+
this.label = label;
|
|
3203
|
+
this.startedAt = startedAt;
|
|
3204
|
+
}
|
|
3205
|
+
req;
|
|
3206
|
+
url;
|
|
3207
|
+
transport;
|
|
3208
|
+
metrics;
|
|
3209
|
+
pool;
|
|
3210
|
+
adaptiveLimiter;
|
|
3211
|
+
timerWheel;
|
|
3212
|
+
timeoutMs;
|
|
3213
|
+
env;
|
|
3214
|
+
cb;
|
|
3215
|
+
previousSignal;
|
|
3216
|
+
controller;
|
|
3217
|
+
signal;
|
|
3218
|
+
label;
|
|
3219
|
+
startedAt;
|
|
3220
|
+
done = false;
|
|
3221
|
+
cancelInner = void 0;
|
|
3222
|
+
timerHandle = void 0;
|
|
3223
|
+
timeoutHandle = void 0;
|
|
3224
|
+
lease = void 0;
|
|
3225
|
+
finish(outcome, exit, error) {
|
|
3226
|
+
if (this.done) return;
|
|
3227
|
+
this.done = true;
|
|
3228
|
+
if (this.timerHandle) {
|
|
3229
|
+
this.timerHandle.cancel();
|
|
3230
|
+
this.timerHandle = void 0;
|
|
3231
|
+
}
|
|
3232
|
+
if (this.timeoutHandle !== void 0) {
|
|
3233
|
+
clearTimeout(this.timeoutHandle);
|
|
3234
|
+
this.timeoutHandle = void 0;
|
|
3235
|
+
}
|
|
3236
|
+
if (this.previousSignal) this.previousSignal.removeEventListener("abort", this.boundAbortFromPrevious);
|
|
3237
|
+
this.cancelInner = void 0;
|
|
3238
|
+
recordAbortablePromiseFinish(this.label, outcome);
|
|
3239
|
+
this.metrics.onFinish({
|
|
3240
|
+
label: this.label,
|
|
3241
|
+
outcome,
|
|
3242
|
+
durationMs: Math.round(performance.now() - this.startedAt),
|
|
3243
|
+
error
|
|
3244
|
+
});
|
|
3245
|
+
this.cb(exit);
|
|
3246
|
+
}
|
|
3247
|
+
finishFailure(error, outcome = "failure") {
|
|
3248
|
+
this.finish(outcome, { _tag: "Failure", cause: Cause.fail(error) }, error);
|
|
3249
|
+
}
|
|
3250
|
+
abortCurrent(reason) {
|
|
3251
|
+
if (!this.controller) return;
|
|
3252
|
+
try {
|
|
3253
|
+
this.controller.abort(reason);
|
|
3254
|
+
} catch {
|
|
3255
|
+
this.controller.abort();
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
/**
|
|
3259
|
+
* Bound reference for addEventListener/removeEventListener.
|
|
3260
|
+
* Only allocated when previousSignal is present (not on the hot path).
|
|
3261
|
+
*/
|
|
3262
|
+
boundAbortFromPrevious = void 0;
|
|
3263
|
+
abortFromPrevious() {
|
|
3264
|
+
const error = this.previousSignal ? abortErrorForSignal(this.previousSignal) : { _tag: "Abort" };
|
|
3265
|
+
this.abortCurrent(this.previousSignal?.reason);
|
|
3266
|
+
const cancel = this.cancelInner;
|
|
3267
|
+
this.cancelInner = void 0;
|
|
3268
|
+
if (this.lease) {
|
|
3269
|
+
releaseFailure(this.lease, this.adaptiveLimiter);
|
|
3270
|
+
this.lease = void 0;
|
|
3271
|
+
}
|
|
3272
|
+
queueMicrotask(() => {
|
|
3273
|
+
this.finishFailure(error);
|
|
3274
|
+
cancel?.();
|
|
3275
|
+
});
|
|
3276
|
+
}
|
|
3277
|
+
runTransportInner() {
|
|
3278
|
+
const { transport, req, url, signal, env } = this;
|
|
3279
|
+
try {
|
|
3280
|
+
const effect = transport({ request: req, url, signal });
|
|
3281
|
+
if (effect._tag === "Succeed") {
|
|
3282
|
+
if (this.lease) releaseSuccess(this.lease, this.adaptiveLimiter, effect.value);
|
|
3283
|
+
this.finish("success", { _tag: "Success", value: effect.value });
|
|
3284
|
+
} else if (effect._tag === "Fail") {
|
|
3285
|
+
if (this.lease) releaseFailure(this.lease, this.adaptiveLimiter);
|
|
3286
|
+
this.finishFailure(effect.error);
|
|
3287
|
+
} else if (effect._tag === "Async") {
|
|
3288
|
+
try {
|
|
3289
|
+
const cancel = effect.register(env, (exit) => {
|
|
3290
|
+
if (this.done) return;
|
|
3291
|
+
this.cancelInner = void 0;
|
|
3292
|
+
if (exit._tag === "Success") {
|
|
3293
|
+
if (this.lease) releaseSuccess(this.lease, this.adaptiveLimiter, exit.value);
|
|
3294
|
+
this.finish("success", exit);
|
|
3295
|
+
} else {
|
|
3296
|
+
if (this.lease) releaseFailure(this.lease, this.adaptiveLimiter);
|
|
3297
|
+
this.finish("failure", exit, exitError(exit));
|
|
3298
|
+
}
|
|
3299
|
+
});
|
|
3300
|
+
if (!this.done) this.cancelInner = typeof cancel === "function" ? cancel : void 0;
|
|
3301
|
+
} catch (error) {
|
|
3302
|
+
if (this.lease) releaseFailure(this.lease, this.adaptiveLimiter);
|
|
3303
|
+
this.finishFailure(normalizeHttpError(error));
|
|
3304
|
+
}
|
|
3305
|
+
} else {
|
|
3306
|
+
const innerCancel = registerHttpEffect(
|
|
3307
|
+
effect,
|
|
3308
|
+
env,
|
|
3309
|
+
(exit) => {
|
|
3310
|
+
if (this.done) return;
|
|
3311
|
+
this.cancelInner = void 0;
|
|
3312
|
+
if (exit._tag === "Success") {
|
|
3313
|
+
if (this.lease) releaseSuccess(this.lease, this.adaptiveLimiter, exit.value);
|
|
3314
|
+
this.finish("success", exit);
|
|
3315
|
+
} else {
|
|
3316
|
+
if (this.lease) releaseFailure(this.lease, this.adaptiveLimiter);
|
|
3317
|
+
this.finish("failure", exit, exitError(exit));
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
);
|
|
3321
|
+
if (!this.done) this.cancelInner = innerCancel;
|
|
3322
|
+
}
|
|
3323
|
+
} catch (error) {
|
|
3324
|
+
if (this.lease) releaseFailure(this.lease, this.adaptiveLimiter);
|
|
3325
|
+
this.finishFailure(normalizeHttpError(error));
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
};
|
|
3329
|
+
var runPoolTransport = (req, url, transport, metrics, pool, adaptiveLimiter, timerWheel, timeoutMs) => ({
|
|
3330
|
+
_tag: "Async",
|
|
3331
|
+
register: (env, cb) => {
|
|
3332
|
+
const previousSignal = req.init?.signal;
|
|
3333
|
+
const controller = new AbortController();
|
|
3334
|
+
const signal = controller.signal;
|
|
3335
|
+
const label = fetchLabel(req, url);
|
|
3336
|
+
const startedAt = performance.now();
|
|
3337
|
+
const state = new PoolRequestState(
|
|
3338
|
+
req,
|
|
3339
|
+
url,
|
|
3340
|
+
transport,
|
|
3341
|
+
metrics,
|
|
3342
|
+
pool,
|
|
3343
|
+
adaptiveLimiter,
|
|
3344
|
+
timerWheel,
|
|
3345
|
+
timeoutMs,
|
|
3346
|
+
env,
|
|
3347
|
+
cb,
|
|
3348
|
+
previousSignal,
|
|
3349
|
+
controller,
|
|
3350
|
+
signal,
|
|
3351
|
+
label,
|
|
3352
|
+
startedAt
|
|
3353
|
+
);
|
|
3354
|
+
recordAbortablePromiseStart(label);
|
|
3355
|
+
metrics.onStart();
|
|
3356
|
+
if (previousSignal) {
|
|
3357
|
+
state.boundAbortFromPrevious = () => state.abortFromPrevious();
|
|
3358
|
+
if (previousSignal.aborted) {
|
|
3359
|
+
state.abortFromPrevious();
|
|
3360
|
+
return () => void 0;
|
|
3361
|
+
}
|
|
3362
|
+
previousSignal.addEventListener("abort", state.boundAbortFromPrevious, ONCE_OPTIONS);
|
|
3363
|
+
}
|
|
3364
|
+
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
3365
|
+
const onTimeout = () => {
|
|
3366
|
+
const reason = timeoutReason(req, url, timeoutMs);
|
|
3367
|
+
if (state.lease) {
|
|
3368
|
+
releaseFailure(state.lease, adaptiveLimiter);
|
|
3369
|
+
state.lease = void 0;
|
|
3370
|
+
}
|
|
3371
|
+
const cancel = state.cancelInner;
|
|
3372
|
+
state.cancelInner = void 0;
|
|
3373
|
+
state.finishFailure(reason, "timeout");
|
|
3374
|
+
state.abortCurrent(reason);
|
|
3375
|
+
cancel?.();
|
|
3376
|
+
};
|
|
3377
|
+
if (timerWheel) {
|
|
3378
|
+
state.timerHandle = timerWheel.schedule(timeoutMs, onTimeout, startedAt);
|
|
3379
|
+
} else {
|
|
3380
|
+
state.timeoutHandle = setTimeout(onTimeout, timeoutMs);
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
if (adaptiveLimiter) {
|
|
3384
|
+
const key = resolveHttpPoolKey(adaptiveLimiter.keyResolver, req, url);
|
|
3385
|
+
const syncLease = adaptiveLimiter.tryAcquireSync(key, signal);
|
|
3386
|
+
if (syncLease) {
|
|
3387
|
+
state.lease = syncLease;
|
|
3388
|
+
} else {
|
|
3389
|
+
adaptiveLimiter.acquire(key, signal, { priority: requestPriority(req) }).then(
|
|
3390
|
+
(asyncLease) => {
|
|
3391
|
+
if (state.done) {
|
|
3392
|
+
asyncLease.release(0);
|
|
3393
|
+
return;
|
|
3394
|
+
}
|
|
3395
|
+
state.lease = asyncLease;
|
|
3396
|
+
state.runTransportInner();
|
|
3397
|
+
},
|
|
3398
|
+
(err) => {
|
|
3399
|
+
if (state.done) return;
|
|
3400
|
+
state.finishFailure(normalizeHttpError(err));
|
|
3401
|
+
}
|
|
3402
|
+
);
|
|
3403
|
+
return () => {
|
|
3404
|
+
if (state.done) return;
|
|
3405
|
+
const cancel = state.cancelInner;
|
|
3406
|
+
state.cancelInner = void 0;
|
|
3407
|
+
state.abortCurrent();
|
|
3408
|
+
if (state.lease) releaseFailure(state.lease, adaptiveLimiter);
|
|
3409
|
+
state.finish("interrupt", { _tag: "Failure", cause: Cause.interrupt() });
|
|
3410
|
+
cancel?.();
|
|
3411
|
+
};
|
|
3412
|
+
}
|
|
3413
|
+
} else if (pool) {
|
|
3414
|
+
const key = resolveHttpPoolKey(pool.keyResolver, req, url);
|
|
3415
|
+
const syncLease = pool.tryAcquireSync(key, signal);
|
|
3416
|
+
if (syncLease) {
|
|
3417
|
+
state.lease = syncLease;
|
|
3418
|
+
} else {
|
|
3419
|
+
pool.acquire(key, signal).then(
|
|
3420
|
+
(asyncLease) => {
|
|
3421
|
+
if (state.done) {
|
|
3422
|
+
asyncLease.release();
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
state.lease = asyncLease;
|
|
3426
|
+
state.runTransportInner();
|
|
3427
|
+
},
|
|
3428
|
+
(err) => {
|
|
3429
|
+
if (state.done) return;
|
|
3430
|
+
state.finishFailure(normalizeHttpError(err));
|
|
3431
|
+
}
|
|
3432
|
+
);
|
|
3433
|
+
return () => {
|
|
3434
|
+
if (state.done) return;
|
|
3435
|
+
const cancel = state.cancelInner;
|
|
3436
|
+
state.cancelInner = void 0;
|
|
3437
|
+
state.abortCurrent();
|
|
3438
|
+
if (state.lease) releaseFailure(state.lease, void 0);
|
|
3439
|
+
state.finish("interrupt", { _tag: "Failure", cause: Cause.interrupt() });
|
|
3440
|
+
cancel?.();
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
state.runTransportInner();
|
|
3445
|
+
return () => {
|
|
3446
|
+
if (state.done) return;
|
|
3447
|
+
const cancel = state.cancelInner;
|
|
3448
|
+
state.cancelInner = void 0;
|
|
3449
|
+
state.abortCurrent();
|
|
3450
|
+
if (state.lease) releaseFailure(state.lease, adaptiveLimiter);
|
|
3451
|
+
state.finish("interrupt", { _tag: "Failure", cause: Cause.interrupt() });
|
|
3452
|
+
cancel?.();
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
});
|
|
3185
3456
|
function makeHttp(cfg = {}) {
|
|
3186
3457
|
validateMakeHttpConfig(cfg);
|
|
3187
3458
|
const baseUrl = cfg.baseUrl ?? "";
|
|
@@ -3200,42 +3471,7 @@ function makeHttp(cfg = {}) {
|
|
|
3200
3471
|
if (!adaptiveLimiter && !pool && timeoutMs === void 0) {
|
|
3201
3472
|
return runDirectTransport(req, url, transport, metrics);
|
|
3202
3473
|
}
|
|
3203
|
-
return
|
|
3204
|
-
async (signal, env) => {
|
|
3205
|
-
let lease;
|
|
3206
|
-
const linkedSignal = linkAbortSignals(signal, req.init?.signal);
|
|
3207
|
-
try {
|
|
3208
|
-
if (linkedSignal.signal.aborted) throw abortErrorForSignal(linkedSignal.signal);
|
|
3209
|
-
if (adaptiveLimiter) {
|
|
3210
|
-
const key = resolveHttpPoolKey(adaptiveLimiter.keyResolver, req, url);
|
|
3211
|
-
lease = await adaptiveLimiter.acquire(key, linkedSignal.signal, { priority: requestPriority(req) });
|
|
3212
|
-
} else if (pool) {
|
|
3213
|
-
const key = resolveHttpPoolKey(pool.keyResolver, req, url);
|
|
3214
|
-
lease = await pool.acquire(key, linkedSignal.signal);
|
|
3215
|
-
}
|
|
3216
|
-
const response = await runTransportEffect(
|
|
3217
|
-
transport({ request: req, url, signal: linkedSignal.signal }),
|
|
3218
|
-
env,
|
|
3219
|
-
linkedSignal.signal
|
|
3220
|
-
);
|
|
3221
|
-
lease = releaseSuccess(lease, adaptiveLimiter, response);
|
|
3222
|
-
return response;
|
|
3223
|
-
} finally {
|
|
3224
|
-
linkedSignal.cleanup();
|
|
3225
|
-
if (lease) {
|
|
3226
|
-
releaseFailure(lease, adaptiveLimiter);
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
|
-
},
|
|
3230
|
-
normalizeHttpError,
|
|
3231
|
-
{
|
|
3232
|
-
label: fetchLabel(req, url),
|
|
3233
|
-
timeoutMs,
|
|
3234
|
-
timeoutReason: timeoutMs ? () => timeoutReason(req, url, timeoutMs) : void 0,
|
|
3235
|
-
onStart: metrics.onStart,
|
|
3236
|
-
onFinish: metrics.onFinish
|
|
3237
|
-
}
|
|
3238
|
-
);
|
|
3474
|
+
return runPoolTransport(req, url, transport, metrics, pool, adaptiveLimiter, void 0, timeoutMs);
|
|
3239
3475
|
};
|
|
3240
3476
|
const metadata = {};
|
|
3241
3477
|
if (adaptiveLimiter) metadata.adaptiveLimiter = adaptiveLimiter;
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-RZQK32C6.js";
|
|
5
5
|
import {
|
|
6
6
|
withHttpObservability
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BHX4MD35.js";
|
|
8
8
|
import {
|
|
9
9
|
makeObservability
|
|
10
10
|
} from "./chunk-TAPB4NN5.js";
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
makeDefaultHttpClient,
|
|
16
16
|
makeHttp
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-HKLZJ5UK.js";
|
|
18
18
|
import {
|
|
19
19
|
Runtime,
|
|
20
20
|
Scheduler
|