Skip to content

Commit

Permalink
refactor(internal): move async helpers using AggregateError to node (#…
Browse files Browse the repository at this point in the history
…9508)

closes: #9504

## Description

While #9275 switched to a global `AggregrateError` constructor, it was unclear if that was safe because that global is not currently available in xsnap. Instead of reverting, this PR moves the "shared" internal helpers that used an `AggregateError` to the `node` folder, to make it clear they should only be used in that environment. All usages of these helpers were already in Node only.

All other usages of `AggregateError` were audited to confirm they only happened on Node as well.

### Security Considerations
None

### Scaling Considerations
None

### Documentation Considerations
None

### Testing Considerations
We're unfortunately lacking coverage of some code under xsnap, Open to ideas on how to improve testing here

It would also be nice somehow to make sure the `node` folders do no end up imported in bundled code. Maybe having a pseudo `import 'node:process'` could work? @kriskowal 

### Upgrade Considerations
None
  • Loading branch information
mhofman committed Jun 22, 2024
1 parent 8650d43 commit ee6f344
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 42 deletions.
46 changes: 46 additions & 0 deletions packages/internal/src/node/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// @ts-check
// @jessie-check

// These tools seem cross platform, but they rely on AggregateError in the error
// handling path, which is currently not available in xsnap
import 'node:process';

/**
* @template T
* @param {readonly (T | PromiseLike<T>)[]} items
* @returns {Promise<T[]>}
*/
export const PromiseAllOrErrors = async items => {
return Promise.allSettled(items).then(results => {
const errors = /** @type {PromiseRejectedResult[]} */ (
results.filter(({ status }) => status === 'rejected')
).map(result => result.reason);
if (!errors.length) {
return /** @type {PromiseFulfilledResult<T>[]} */ (results).map(
result => result.value,
);
} else if (errors.length === 1) {
throw errors[0];
} else {
throw AggregateError(errors);
}
});
};

/**
* @type {<T>(
* trier: () => Promise<T>,
* finalizer: (error?: unknown) => Promise<void>,
* ) => Promise<T>}
*/
export const aggregateTryFinally = async (trier, finalizer) =>
trier().then(
async result => finalizer().then(() => result),
async tryError =>
finalizer(tryError)
.then(
() => tryError,
finalizeError => AggregateError([tryError, finalizeError]),
)
.then(error => Promise.reject(error)),
);
40 changes: 0 additions & 40 deletions packages/internal/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,46 +74,6 @@ export const makeMeasureSeconds = currentTimeMillisec => {
return measureSeconds;
};

/**
* @template T
* @param {readonly (T | PromiseLike<T>)[]} items
* @returns {Promise<T[]>}
*/
export const PromiseAllOrErrors = async items => {
return Promise.allSettled(items).then(results => {
const errors = /** @type {PromiseRejectedResult[]} */ (
results.filter(({ status }) => status === 'rejected')
).map(result => result.reason);
if (!errors.length) {
return /** @type {PromiseFulfilledResult<T>[]} */ (results).map(
result => result.value,
);
} else if (errors.length === 1) {
throw errors[0];
} else {
throw AggregateError(errors);
}
});
};

/**
* @type {<T>(
* trier: () => Promise<T>,
* finalizer: (error?: unknown) => Promise<void>,
* ) => Promise<T>}
*/
export const aggregateTryFinally = async (trier, finalizer) =>
trier().then(
async result => finalizer().then(() => result),
async tryError =>
finalizer(tryError)
.then(
() => tryError,
finalizeError => AggregateError([tryError, finalizeError]),
)
.then(error => Promise.reject(error)),
);

/**
* @template {Record<string, unknown>} T
* @typedef {{ [P in keyof T]: Exclude<T[P], undefined> }} AllDefined
Expand Down
5 changes: 4 additions & 1 deletion packages/swing-store/src/snapStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { finished as finishedCallback, PassThrough, Readable } from 'stream';
import { promisify } from 'util';
import { createGzip, createGunzip } from 'zlib';
import { Fail, q } from '@agoric/assert';
import { aggregateTryFinally, PromiseAllOrErrors } from '@agoric/internal';
import {
aggregateTryFinally,
PromiseAllOrErrors,
} from '@agoric/internal/src/node/utils.js';
import { buffer } from './util.js';

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/telemetry/src/make-slog-sender.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'path';
import tmp from 'tmp';
import { PromiseAllOrErrors } from '@agoric/internal';
import { PromiseAllOrErrors } from '@agoric/internal/src/node/utils.js';
import { serializeSlogObj } from './serialize-slog-obj.js';

export const DEFAULT_SLOGSENDER_MODULE =
Expand Down

0 comments on commit ee6f344

Please sign in to comment.