Repository navigation
fix(gax): suppress cancel destroy error in StreamArrayParser and add Bun certification matrix - #9652
danieljbruce wants to merge 2 commits into
Conversation
…Bun certification matrix
There was a problem hiding this comment.
Code Review
This pull request implements a _destroy method in StreamArrayParser to handle cancellation requests and introduces a comprehensive Bun certification test suite. The reviewer feedback highlights several improvement opportunities in the new test file, including explicitly importing the protobufjs library to resolve missing type references, replacing a flaky hardcoded setTimeout with a robust listener on the stream's 'close' event, and wrapping heap-growth assertions in checks that verify globalThis.gc is available to prevent failures in environments where garbage collection is not exposed.
| import * as grpc from '@grpc/grpc-js'; | ||
| import {PassThroughClient} from 'google-auth-library'; |
There was a problem hiding this comment.
The test file references protobuf.Service and protobuf.Message but does not import the protobufjs library. To prevent compilation errors or reliance on ambient globals, please explicitly import protobufjs.
| import * as grpc from '@grpc/grpc-js'; | |
| import {PassThroughClient} from 'google-auth-library'; | |
| import * as grpc from '@grpc/grpc-js'; | |
| import * as protobuf from 'protobufjs'; | |
| import {PassThroughClient} from 'google-auth-library'; |
| }); | ||
| parser.on('error', (err: Error) => streamErrors.push(err)); | ||
|
|
||
| await new Promise<void>(resolve => setTimeout(resolve, 120)); |
There was a problem hiding this comment.
Using a hardcoded setTimeout of 120ms to wait for stream teardown can lead to flaky test failures in slow or resource-constrained CI environments. Since parser.cancel() internally calls this.destroy(), you can robustly wait for the stream's 'close' event instead.
| await new Promise<void>(resolve => setTimeout(resolve, 120)); | |
| await new Promise<void>(resolve => parser.on('close', resolve)); |
| assert.ok( | ||
| heapDeltaMb < 40, | ||
| `Expected bounded LRO heap growth (<40MB), got ${heapDeltaMb.toFixed(2)}MB`, | ||
| ); |
There was a problem hiding this comment.
Checking heap growth without a guaranteed garbage collection run can lead to flaky test failures in CI environments where globalThis.gc is not exposed (i.e., when Node.js is not run with the --expose-gc flag). To make this test robust, please wrap the heap delta assertion in a check that ensures globalThis.gc is available.
if (typeof globalThis.gc === 'function') {
assert.ok(
heapDeltaMb < 40,
'Expected bounded LRO heap growth (<40MB), got ' + heapDeltaMb.toFixed(2) + 'MB',
);
}| assert.ok( | ||
| heapDeltaMb < 40, | ||
| `Expected bounded read heap growth (<40MB), got ${heapDeltaMb.toFixed(2)}MB`, | ||
| ); |
There was a problem hiding this comment.
Similar to the LRO polling test, checking heap growth without a guaranteed garbage collection run can lead to flaky test failures in CI environments where globalThis.gc is not exposed. Please wrap this heap delta assertion in a check that ensures globalThis.gc is available.
if (typeof globalThis.gc === 'function') {
assert.ok(
heapDeltaMb < 40,
'Expected bounded read heap growth (<40MB), got ' + heapDeltaMb.toFixed(2) + 'MB',
);
}…rrayParser._destroy unit test
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive Bun certification and compatibility test suite (bunCertificationMatrix.ts) to verify package installation, transport parity (gRPC and REST fallback), streaming integrity, and resource stability under Bun. It also implements a _destroy method in StreamArrayParser to properly handle stream cancellation without emitting unexpected errors, along with corresponding unit tests. The review feedback suggests dynamically detecting the Bun lockfile name (bun.lock vs. bun.lockb) to ensure compatibility with Bun versions prior to 1.2.0.
| function requireBun(ctx: Context): string { | ||
| if (!bunBin) { | ||
| ctx.skip(); | ||
| } | ||
| return bunBin!; | ||
| } |
There was a problem hiding this comment.
In Bun versions prior to 1.2.0, the default lockfile format is binary and named bun.lockb instead of bun.lock. To ensure the certification matrix is compatible across all Bun versions (both pre-1.2 and 1.2+), we should dynamically detect and use the correct lockfile name.
function requireBun(ctx: Context): string {
if (!bunBin) {
ctx.skip();
}
return bunBin!;
}
function getLockfilename(dir: string): string {
return fs.existsSync(path.join(dir, 'bun.lock')) ? 'bun.lock' : 'bun.lockb';
}| {cwd: coldDir, encoding: 'utf8', timeout: 60000}, | ||
| ); | ||
| assert.strictEqual(coldRes.status, 0, coldRes.stderr); | ||
| assert.ok(fs.existsSync(path.join(coldDir, 'bun.lock'))); |
| const lockHashA = crypto | ||
| .createHash('sha256') | ||
| .update(fs.readFileSync(path.join(cleanA, 'bun.lock'))) | ||
| .digest('hex'); | ||
| const lockHashB = crypto | ||
| .createHash('sha256') | ||
| .update(fs.readFileSync(path.join(cleanB, 'bun.lock'))) | ||
| .digest('hex'); | ||
| assert.strictEqual(lockHashA, lockHashB); |
There was a problem hiding this comment.
Use the dynamic lockfile name helper to support both bun.lock and bun.lockb depending on the Bun version.
| const lockHashA = crypto | |
| .createHash('sha256') | |
| .update(fs.readFileSync(path.join(cleanA, 'bun.lock'))) | |
| .digest('hex'); | |
| const lockHashB = crypto | |
| .createHash('sha256') | |
| .update(fs.readFileSync(path.join(cleanB, 'bun.lock'))) | |
| .digest('hex'); | |
| assert.strictEqual(lockHashA, lockHashB); | |
| const lockFilenameA = getLockfilename(cleanA); | |
| const lockHashA = crypto | |
| .createHash('sha256') | |
| .update(fs.readFileSync(path.join(cleanA, lockFilenameA))) | |
| .digest('hex'); | |
| const lockFilenameB = getLockfilename(cleanB); | |
| const lockHashB = crypto | |
| .createHash('sha256') | |
| .update(fs.readFileSync(path.join(cleanB, lockFilenameB))) | |
| .digest('hex'); | |
| assert.strictEqual(lockHashA, lockHashB); |
| const lockHashAfter = crypto | ||
| .createHash('sha256') | ||
| .update(fs.readFileSync(path.join(cleanA, 'bun.lock'))) | ||
| .digest('hex'); | ||
| assert.strictEqual(lockHashAfter, lockHashA); |
There was a problem hiding this comment.
Use the dynamic lockfile name helper to support both bun.lock and bun.lockb depending on the Bun version.
const lockFilenameAfter = getLockfilename(cleanA);
const lockHashAfter = crypto
.createHash('sha256')
.update(fs.readFileSync(path.join(cleanA, lockFilenameAfter)))
.digest('hex');
assert.strictEqual(lockHashAfter, lockHashA);| fs.copyFileSync( | ||
| path.join(ciEnv1, 'package.json'), | ||
| path.join(ciEnv2, 'package.json'), | ||
| ); | ||
| fs.copyFileSync( | ||
| path.join(ciEnv1, 'bun.lock'), | ||
| path.join(ciEnv2, 'bun.lock'), | ||
| ); |
There was a problem hiding this comment.
Use the dynamic lockfile name helper to support both bun.lock and bun.lockb depending on the Bun version.
// Copy package.json + lockfile to second isolated CI runner directory
const lockFilename = getLockfilename(ciEnv1);
fs.copyFileSync(
path.join(ciEnv1, 'package.json'),
path.join(ciEnv2, 'package.json'),
);
fs.copyFileSync(
path.join(ciEnv1, lockFilename),
path.join(ciEnv2, lockFilename),
);
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:
Fixes b/574441098
Fixes b/574456004
Summary
This PR adds the automated Bun Full Matrix Sign-off & Certification Test Suite (
core/packages/gax/test/unit/bunCertificationMatrix.ts) and fixes a stream teardown bug inStreamArrayParseruncovered during real-HTTP server-streaming cancellation testing (b/574456004):StreamArrayParser._destroyCancellation Fix (core/packages/gax/src/streamArrayParser.ts):streamArrayParser.cancel()is invoked on an active HTTP/1.1 REST fallback server stream backed by a realnode-fetchresponse body,cancelController.abort()causesnode-fetchto destroyresponse.bodywithAbortError.stream.pipelineforwards that error tostreamArrayParser.destroy(err), which previously emitted a spuriousAbortErrorevent onstreamArrayParserdespitecancelRequestedbeingtrue._destroy(err, callback)to passnullwhenthis.cancelRequestedistruesuppresses the spuriousAbortErrorwhile preserving error propagation for non-cancelled stream failures and timeouts.core/packages/gax/test/unit/bunCertificationMatrix.ts):bun installreliability and warm-cache installation speed, lockfile (bun.lock) determinism across clean installs and--frozen-lockfileCI environments,postinstall/preparelifecycle hook ordering andtrustedDependencies, and monorepoworkspaces(workspace:*) resolution.grpc-status-details-bin/ErrorInfodecoding, canonical status code preservation, deadlines (DEADLINE_EXCEEDED), and unary/streaming cancellations (CANCELLED)..pause()/.resume()flow control.LROperationpolling and regular read workloads with bounded heap delta, zero leaked file descriptors/sockets, low p95 latency, and zerounhandledRejectionevents.K_SERVICE/PORT), and Firebase (FIREBASE_CONFIG/GCLOUD_PROJECT).