Skip to content

Commit

Permalink
document Promise temporality and VowTools (#10141)
Browse files Browse the repository at this point in the history
_incidental_

## Description
Document VowTools and also the kinds of Promises that one has to keep in mind when working with durable promises (vows).

### Security Considerations
none

### Scaling Considerations
none

### Documentation Considerations
We might point to this reference material from docs.agoric.com

### Testing Considerations
n/a

### Upgrade Considerations
n/a
  • Loading branch information
mergify[bot] authored Sep 26, 2024
2 parents 43dd3ec + 392795f commit 0b1501a
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 4 deletions.
92 changes: 92 additions & 0 deletions packages/SwingSet/docs/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Asynchronous Considerations in SwingSet

When working with SwingSet, it's crucial to understand how asynchronous operations interact with the system's execution model. A **vat** (a unit of isolated execution in SwingSet) may restart or terminate before an asynchronous operation completes. Although JavaScript's `await` keyword allows you to write asynchronous code that resembles synchronous code, you must be mindful of events that could prevent a promise from settling:

- **Underlying condition never satisfied**: The condition required for the promise to settle may never occur.
- **Vat terminates, restarts, or upgrades before settlement**: The promise will be severed if its decider vat terminates, restarts, or upgrades before it settles.

# Promise Temporality

When programming with promises in SwingSet, it's important to understand the **temporality** of promise settlement—that is, the timing and execution dependencies required for a promise to settle. Promises can be classified into three categories based on their temporality: **Immediate**, **Prompt**, and **Delayed**.

## Immediate Promises

An **Immediate** promise is one that settles within the same Crank as its creation (see [Kernel Cycles](./state.md)). We refer to this as "Immediate" because it occurs before any further message deliveries into the vat, similar to how all promise reaction callbacks happen before any JavaScript event callbacks (such as those queued by `setImmediate`).

### Characteristics of Immediate Promises

- **Settlement Timing**: Settles within the same crank it was created.
- **Dependencies**: Requires no additional input to the "vat"; all information is locally available.
- **Safety**: Not affected by vat restarts or upgrades, which occur in their own crank.

## Prompt Promises

A **Prompt** promise settles without requiring further input from outside SwingSet. All Immediate promises are also Prompt, but not all Prompt promises are Immediate.

The SwingSet kernel processes its run queue items until there is nothing left to run. Most hosts, like cosmic-swingset, do not inject new items on the SwingSet run queue until it's empty, and instead maintain their own queue of input events. In those cases the SwingSet kernel executes similarly to how a vat executes a crank: an external I/O event triggers some execution, and the resulting SwingSet run queue items are drained before returning to the host for the next I/O.

While any of these queue items can technically cause a vat to upgrade, most systems running on SwingSet will trigger a vat upgrade based on some I/O input. As such, Prompt promises are unaffected by vat upgrades, unless the vat getting upgraded somehow got involved in processing the I/O that triggered its own upgrade.

### Characteristics of Prompt Promises

- **Settlement Timing**: Settles before any new I/O is processed.
- **Dependencies**: May involve multiple cranks but does not require external input.
- **Safety**: Safe from being severed by vat upgrades initiated by external I/O.

### Factors That Can Poison Promptness

The following are examples of I/O that can prevent a promise from being Prompt (they "poison" promptness):

- **Waiting on a timer**
- **Waiting on an inter-chain network call**
- **Waiting on a bid being placed**
- **Waiting on a new oracle price**

### Factors That Do Not Affect Promptness

- **Upgrade**: cosmic-swingset ensures quiescence between I/O and vat upgrades are triggered only when processing a new item from the chain's action queue (such as network input).
- **Bridge Calls**: Bridge calls are Prompt. While the caller may subsequently wait for an acknowledgment input (which is not Prompt), the bridge call itself does not affect promptness.

## Delayed Promises

A **Delayed** promise is one that requires additional input or external events to settle. It does not settle within the same crank and depends on factors not available during the current run of SwingSet. Delayed promises are neither Immediate nor Prompt because they involve waiting for external dependencies or future events beyond the current execution cycles.

### Characteristics of Delayed Promises

- **Settlement Timing**: Settles in future cranks after external events or inputs occur.
- **Dependencies**: Requires external input or events outside of SwingSet to settle.
- **Safety**: More susceptible to vat restarts, upgrades, or terminations affecting their settlement.

### Common Scenarios for Delayed Promises

- **Waiting on a Timer**: Introduces a delay until a specified time, relying on external scheduling.
- **Waiting on External Data or Events**: Depends on input from outside the SwingSet environment, such as a new oracle price.
- **User Actions**: Requires actions from external users, like placing a bid.
- **Network Communications**: Involves waiting for responses from external systems or networks, such as inter-chain network calls.

### Contrast with Immediate and Prompt Promises

- **Immediate Promises**:
- **Settlement**: Within the same crank.
- **Dependencies**: No external dependencies; all information is available within the "vat".
- **Example**: A calculation based on existing in-vat data.

- **Prompt Promises**:
- **Settlement**: Before any new I/O occurs, possibly over multiple cranks.
- **Dependencies**: No external input required, but may involve inter-vat asynchronous operations.
- **Example**: Internal message passing between vats that settles before any new external events.

- **Delayed Promises**:
- **Settlement**: After external events or inputs are received, over multiple cranks.
- **Dependencies**: Requires external input, events, or time to pass.
- **Example**: Waiting for a user to place a bid or for an oracle to provide new data.

## Summary

Understanding the temporality of promises in SwingSet helps you write robust asynchronous code that behaves predictably, even in the face of vat restarts, upgrades, or terminations. By being aware of the differences between Immediate, Prompt, and Delayed promises, you can design your smart contracts and applications to be more resilient and efficient.

- **Immediate Promises** are settled within the same crank and are safe from vat restarts or upgrades during that crank.
- **Prompt Promises** settle without external input before any new I/O, making them safe from being severed by vat upgrades initiated by external events.
- **Delayed Promises** depend on external input or events and settle over future cranks, making them more vulnerable to disruptions like vat restarts or terminations.

By classifying your promises appropriately, you can better manage asynchronous operations and ensure the reliability of your SwingSet applications.
38 changes: 34 additions & 4 deletions packages/vow/src/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,36 @@ import { makeWhen } from './when.js';
/**
* @import {Zone} from '@agoric/base-zone';
* @import {Passable} from '@endo/pass-style';
* @import {IsRetryableReason, AsPromiseFunction, EVow, Vow, ERef} from './types.js';
* @import {EUnwrap, IsRetryableReason, AsPromiseFunction, Vow, VowKit, EVow, PromiseVow, Watcher} from './types.js';
*/

/**
* @typedef VowTools
* @property {(maybeVows: unknown[]) => Vow<any[]>} all Vow-tolerant implementation of Promise.all that takes an iterable of vows
* and other {@link Passable}s and returns a single {@link Vow}. It resolves
* with an array of values when all of the input's promises or vows are
* fulfilled and rejects when any of the input's promises or vows are rejected
* with the first rejection reason.
* @property {(maybeVows: unknown[]) => Vow<({status: 'fulfilled', value: any} |
* {status: 'rejected', reason: any})[]>} allSettled Vow-tolerant
* implementation of Promise.allSettled that takes an iterable of vows and other
* {@link Passable}s and returns a single {@link Vow}. It resolves when all of
* the input's promises or vows are settled with an array of settled outcome
* objects.
* @property {(maybeVows: unknown[]) => Vow<any[]>} allVows
* @property {AsPromiseFunction} asPromise Convert a vow or promise to a promise, ensuring proper handling of ephemeral promises.
* @property {<T extends unknown>(fn: (...args: any[]) => Vow<Awaited<T>> |
* Awaited<T> | PromiseVow<T>) => Vow<Awaited<T>>} asVow Helper function that
* coerces the result of a function to a Vow. Helpful for scenarios like a
* synchronously thrown error.
* @property {<T>() => VowKit<T>} makeVowKit
* @property {<F extends (...args: any[]) => Promise<any>>(fnZone: Zone, name: string, fn: F) => F extends (...args: infer Args) => Promise<infer R> ? (...args: Args) => Vow<R> : never} retriable
* @property {<T = any, TResult1 = T, TResult2 = never, C extends any[] = any[]>(specimenP: EVow<T>, watcher?: Watcher<T, TResult1, TResult2, C> | undefined, ...watcherArgs: C) => Vow<Exclude<TResult1, void> | Exclude<TResult2, void> extends never ? TResult1 : Exclude<TResult1, void> | Exclude<TResult2, void>>} watch
* @property {<T, TResult1 = EUnwrap<T>, TResult2 = never>(specimenP: T, onFulfilled?: ((value: EUnwrap<T>) => TResult1 | PromiseLike<TResult1>) | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined) => Promise<TResult1 | TResult2>} when Shorten `specimenP` until we achieve a final result.
*
* Does not survive upgrade (even if specimenP is a durable Vow).
*
* Use only if the Vow will resolve _promptly_ {@see {@link @agoric/swingset-vat/docs/async.md}}.
*/

/**
Expand All @@ -18,6 +47,7 @@ import { makeWhen } from './when.js';
* @param {Zone} zone
* @param {object} [powers]
* @param {IsRetryableReason} [powers.isRetryableReason]
* @returns {VowTools}
*/
export const prepareBasicVowTools = (zone, powers = {}) => {
const { isRetryableReason = /** @type {IsRetryableReason} */ (() => false) } =
Expand All @@ -34,8 +64,10 @@ export const prepareBasicVowTools = (zone, powers = {}) => {
const watchUtils = makeWatchUtils();
const asVow = makeAsVow(makeVowKit);

// FIXME in https://github.com/Agoric/agoric-sdk/pull/9785
/**
* TODO FIXME make this real
* @alpha Not yet implemented
*
* Create a function that retries the given function if the underlying
* functions rejects due to upgrade disconnection.
*
Expand Down Expand Up @@ -96,5 +128,3 @@ export const prepareBasicVowTools = (zone, powers = {}) => {
});
};
harden(prepareBasicVowTools);

/** @typedef {ReturnType<typeof prepareBasicVowTools>} VowTools */

0 comments on commit 0b1501a

Please sign in to comment.