Do not serialize output registration.
This can result in circular promise chains if the output values depend
on promises that will only resolve in later turns.
@@ -131,7 +131,9 @@ export function registerResourceOutputs(res: Resource, outputs: ComputedValues)
const transfer: Promise<PropertyTransfer> = debuggablePromise(
transferProperties(undefined, `completeResource`, outputs, undefined));
- // Now run the operation, serializing the invocation if necessary.
+ // Now run the operation. Note that we explicitly do not serialize output registration with respect to other
+ // resource operations, as outputs may depend on properties of other resources that will not resolve until
+ // later turns. This would create a circular promise chain that can never resolve.
const opLabel = `monitor.registerResourceOutputs(...)`;
runAsyncResourceOp(opLabel, async () => {
// The registration could very well still be taking place, so we will need to wait for its URN. Additionally,
@@ -166,7 +168,7 @@ export function registerResourceOutputs(res: Resource, outputs: ComputedValues)
// If the monitor doesn't exist, still make sure to resolve all properties to undefined.
log.warn(`Not sending RPC to monitor -- it doesn't exist: urn=${urn}`);
}
- });
+ }, false);
}
/**
@@ -179,9 +181,11 @@ let resourceChain: Promise<void> = Promise.resolve();
let resourceChainLabel: string | undefined = undefined;
// runAsyncResourceOp runs an asynchronous resource operation, possibly serializing it as necessary.
-function runAsyncResourceOp(label: string, callback: () => Promise<void>): void {
+function runAsyncResourceOp(label: string, callback: () => Promise<void>, serial?: boolean): void {
// Serialize the invocation if necessary.
- const serial: boolean = serialize();
+ if (serial === undefined) {
+ serial = serialize();
+ }
const resourceOp: Promise<void> = debuggablePromise(resourceChain.then(async () => {
if (serial) {
resourceChainLabel = label;
