Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(daemon): Synchronize host evaluate method #2092

Merged
merged 4 commits into from
Feb 22, 2024

Conversation

rekmarks
Copy link
Contributor

@rekmarks rekmarks commented Feb 21, 2024

Progresses: #2086
Fixes: #2021

Synchronizes the host's evaluate() method by delegating all incarnations to the daemon via incarnateEval(). The latter is responsible for incarnating its dependents as necessary, and for the generation of their formula numbers. To facilitate this, the synchronous methods incarnateLookupSync() and incarnateWorkerSync() have been added. These methods synchronously mutate the formula graph, and return promises that resolve when the formulas have been written to disk. The result is that the formula graph is mutated within a single turn of the event loop.

To achieve this, the implementation introduces new constraints on the daemon and its dependents. #2089 introduced the notion of "incarnating" values. By the current definition, incarnating a value consists of the following steps:

  1. Collecting dependencies (async)
    1. Generating requisite formula numbers (async)
      • We use the asynchronous signature of crypto.randomBytes to do
        this for performance reasons.
    2. Incarnating any dependent values (recursion!)
  2. Updating the in-memory formula graph (sync)
  3. Writing the resulting formula to disk (async)
  4. Reifiying the resulting value (async)

In order to make formula graph mutations mutually exclusive, we introduce a "formula graph mutex" under which step 1 must be performed. This mutex is currently only used by incarnateEval(), and must be expanded to its sibling methods in the future.

incarnateEval() also introduces the notion of "incarnation hooks" to the codebase. Originators of incarnations that are exogenous to the daemon may themselves have asynchronous work perform. For example, petName -> formulaIdentifier mappings are the responsibility of the host and its pet store, and pet names must be associated with their respective formula identifiers the moment that those identifiers are observable to the consumer. To handle sych asynchronous side effects, the implementation introduces a notion of "hooks" to incarnateEval(), with the intention of spreading this to other incarnation methods as necessary. These hooks receive as an argument all formula identifiers created by the incarnation, and are executed under the formula graph mutex. This will surface IO errors to the consumer, and help us uphold the principle of "death before confusion".

Also of note, provideValueForNumberedFormula has been modified such that the formula is written to disk after the controller has been constructed. This is critical in order to synchronize formula graph mutations.

Finally, it appears that the implementation incidentally fixed #2021. We may still wish to adopt the more robust solution proposed in that issue.

@rekmarks rekmarks force-pushed the rekmarks-mutually-exclusive-formula-graph-mutations branch 2 times, most recently from afdfa21 to c4f5572 Compare February 22, 2024 01:38
@rekmarks rekmarks requested a review from kumavis February 22, 2024 01:39
@rekmarks rekmarks marked this pull request as ready for review February 22, 2024 01:39
@rekmarks rekmarks requested a review from kriskowal February 22, 2024 01:39
* @param {string[]} endowmentFormulaIdentifiers
* @param {(string | string[])[]} endowmentFormulaPointers
* @param {import('./types.js').EvalFormulaHook[]} hooks
* @param {string} [specifiedWorkerFormulaIdentifier]
* @returns {Promise<{ formulaIdentifier: string, value: unknown }>}
*/
const incarnateEval = async (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only incarnateXyz() method that is synchronized as of this PR. Just noting that we will probably want to expand the conventions it establishes to its siblings ASAP.

@rekmarks rekmarks force-pushed the rekmarks-mutually-exclusive-formula-graph-mutations branch from c4f5572 to b2522bf Compare February 22, 2024 01:45
Copy link
Member

@kriskowal kriskowal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it closes the particular bug. Does this synchronize all formula writes or is the intention to capture those in follow-ups?

packages/daemon/src/daemon.js Show resolved Hide resolved
packages/daemon/src/daemon.js Outdated Show resolved Hide resolved
packages/daemon/src/daemon.js Outdated Show resolved Hide resolved
packages/daemon/src/daemon.js Outdated Show resolved Hide resolved
packages/daemon/src/daemon.js Outdated Show resolved Hide resolved
packages/daemon/src/daemon.js Outdated Show resolved Hide resolved
packages/daemon/src/daemon.js Outdated Show resolved Hide resolved
packages/daemon/src/host.js Outdated Show resolved Hide resolved
}),
// TODO:lookup Check if a formula already exists for the path. May have to be
// done in the daemon itself.
return petNamePath;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function appears to either return a pet name path or a formula identifier. Is a “pointer” the union of pet name path and identifier? If so, how are they distinguished?

Copy link
Contributor Author

@rekmarks rekmarks Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed the property / array to endowmentFormulaIdsOrPaths. In this particular array, strings are formula identifiers and arrays are pet name paths. They are distinguished using typeof value === 'string'.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, the plural agreement is confusing, but there’s perhaps nothing for it. I would like to consider (in follow-up) whether we can normalize these to formula identifiers up front.

packages/daemon/test/test-endo.js Outdated Show resolved Hide resolved
await incarnateWorkerSync(workerFormulaNumber)
).formulaIdentifier,
endowmentFormulaIdentifiers: await Promise.all(
endowmentFormulaPointers.map(async formulaIdOrPath => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think formulaIdOrPath is a misnomer here. I recommend just using the name petNamePath and just fall through to petName if it’s a string. It should never be a nonce / formula identifier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rekmarks rekmarks force-pushed the rekmarks-mutually-exclusive-formula-graph-mutations branch from bdaf855 to d9c865c Compare February 22, 2024 05:07
@rekmarks
Copy link
Contributor Author

@kriskowal

Does this synchronize all formula writes or is the intention to capture those in follow-ups?

The latter.

@rekmarks rekmarks requested a review from kriskowal February 22, 2024 05:10
@rekmarks
Copy link
Contributor Author

rekmarks commented Feb 22, 2024

ATTN reviewers:

fix(daemon): Correctly use worker formula number in incarnateEval
incarnateEval() was accidentally passing worker formula identifiers instead of formula numbers when it received a specified worker formula identifier. This caused eval formulas to always fail on Windows due to a : in the path.

Surprisingly, fixing this caused the "indirect termination" test to fail. This is likely just surfacing a deeper problem, see #2074.

rekmarks and others added 4 commits February 22, 2024 08:18
Makes `makeControllerForFormula` and its dependent functions
synchronous. Also performs some unused parameter cleanup.
Synchronizes the host's `evaluate()` method by delegating all
incarnations to the daemon via `incarnateEval()`. The latter is
responsible for incarnating its dependents as necessary, and for the
generation of their formula numbers. To facilitate this, the synchronous
methods `incarnateLookupSync()` and `incarnateWorkerSync()` have been
added. These methods synchronously mutate the formula graph, and return
promises that resolve when the formulas have been written to disk. The
result is that the formula graph is mutated within a single turn of the
event loop.

To achieve this, the implementation introduces new constraints on the
daemon and its dependents. #2089 introduced the notion of "incarnating"
values. By the current definition, incarnating a value consists of the
following steps:
1. Collecting dependencies (async)
  a. Generating requisite formula numbers (async)
      - We use the asynchronous signature of `crypto.randomBytes` to do
      this for performance reasons.
  b. Incarnating any dependent values (recursion!)
2. Updating the in-memory formula graph (sync)
3. Writing the resulting formula to disk (async)
4. Reifiying the resulting value (async)

In order to make formula graph mutations mutually exclusive, we
introduce a "formula graph mutex" under which step 1 must be performed.
This mutex is currently only used by `incarnateEval()`, and must be
expanded to its sibling methods in the future.

`incarnateEval()` also introduces the notion of "incarnation hooks" to
the codebase. Originators of incarnations that are exogenous to the
daemon may themselves have asynchronous work perform. For example,
`petName -> formulaIdentifier` mappings are the responsibility of the
host and its pet store, and pet names must be associated with their
respective formula identifiers the moment that those identifiers are
observable to the consumer. To handle sych asynchronous side effects,
the implementation introduces a notion of "hooks" to `incarnateEval()`,
with the intention of spreading this to other incarnation methods as
necessary. These hooks receive as an argument all formula identifiers
created by the incarnation, and are executed under the formula graph
mutex. This will surface IO errors to the consumer, and help us uphold
the principle of "death before confusion".

Also of note, `provideValueForNumberedFormula` has been modified such
that the formula is written to disk _after_ the controller has been
constructed. This is critical in order to synchronize formula graph
mutations.

Finally, it appears that the implementation incidentally fixed #2021.
We may still wish to adopt the more robust solution proposed in that
issue.
`incarnateEval()` was accidentally passing worker formula identifiers
instead of formula numbers when it received a specified worker formula
identifier. This caused `eval` formulas to always fail on Windows due to
a `:` in the path.

Surprisingly, fixing this caused the "indirect termination" test to
fail. This is likely just surfacing a deeper problem, see #2074.
- Rename various entities for clarity / correctness
- Use terser implementation of async flow in
  `provideValueforNumberedFormula`
- Add inline documentation

Co-authored-by: Kris Kowal <kris@agoric.com>
@rekmarks rekmarks force-pushed the rekmarks-mutually-exclusive-formula-graph-mutations branch from d9c865c to fcd9093 Compare February 22, 2024 16:18
}),
// TODO:lookup Check if a formula already exists for the path. May have to be
// done in the daemon itself.
return petNamePath;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, the plural agreement is confusing, but there’s perhaps nothing for it. I would like to consider (in follow-up) whether we can normalize these to formula identifiers up front.

*/
const incarnateLookup = async (hubFormulaIdentifier, petNamePath) => {
const formulaNumber = await randomHex512();
const incarnateNumberedLookup = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be tricky to replicate if we go on as I suggest to make lookup formulas single-hop.

@rekmarks rekmarks merged commit 065f966 into master Feb 22, 2024
14 checks passed
@rekmarks rekmarks deleted the rekmarks-mutually-exclusive-formula-graph-mutations branch February 22, 2024 18:36
rekmarks added a commit that referenced this pull request Mar 8, 2024
Synchronizes the host's `makeUnconfined()` per #2086. Refactoring
`daemon.js` in support of this goal fixed one bug while revealing
another.

In particular, #2074 is progressed by enabling indirect
cancellation of caplets via their workers. The issue is not resolved
since indirect cancellation of caplets via their caplet dependencies
still does not work as intended. A new, failing regression test has been
added for this specific case.

The revealed bug is #2021, which we believed to be fixed by #2092.
Rather than fixing the bug, that PR concealed it by always creating a
new incarnation of `eval` formula workers, even if they already existed.
The regression test for #2021 has been marked as failing, and we will
have to find a different solution for it.
rekmarks added a commit that referenced this pull request Mar 8, 2024
Synchronizes the host's `makeUnconfined()` per #2086. Refactoring
`daemon.js` in support of this goal fixed one bug while revealing
another.

In particular, #2074 is progressed by enabling indirect
cancellation of caplets via their workers. The issue is not resolved
since indirect cancellation of caplets via their caplet dependencies
still does not work as intended. A new, failing regression test has been
added for this specific case.

The revealed bug is #2021, which we believed to be fixed by #2092.
Rather than fixing the bug, that PR concealed it by always creating a
new incarnation of `eval` formula workers, even if they already existed.
The regression test for #2021 has been marked as failing, and we will
have to find a different solution for it.
rekmarks added a commit that referenced this pull request Mar 8, 2024
Synchronizes the host's `makeUnconfined()` per #2086. Refactoring
`daemon.js` in support of this goal fixed one bug while revealing
another.

In particular, #2074 is progressed by enabling indirect
cancellation of caplets via their workers. The issue is not resolved
since indirect cancellation of caplets via their caplet dependencies
still does not work as intended. A new, failing regression test has been
added for this specific case.

The revealed bug is #2021, which we believed to be fixed by #2092.
Rather than fixing the bug, that PR concealed it by always creating a
new incarnation of `eval` formula workers, even if they already existed.
The regression test for #2021 has been marked as failing, and we will
have to find a different solution for it.
rekmarks added a commit that referenced this pull request Mar 8, 2024
Synchronizes the host's `makeUnconfined()` per #2086. Refactoring
`daemon.js` in support of this goal fixed one bug while revealing
another.

In particular, #2074 is progressed by enabling indirect
cancellation of caplets via their workers. The issue is not resolved
since indirect cancellation of caplets via their caplet dependencies
still does not work as intended. A new, failing regression test has been
added for this specific case.

The revealed bug is #2021, which we believed to be fixed by #2092.
Rather than fixing the bug, that PR concealed it by always creating a
new incarnation of `eval` formula workers, even if they already existed.
The regression test for #2021 has been marked as failing, and we will
have to find a different solution for it.
rekmarks added a commit that referenced this pull request Mar 9, 2024
Synchronizes the host's `makeUnconfined()` per #2086. Refactoring
`daemon.js` in support of this goal fixed one bug while revealing
another.

In particular, #2074 is progressed by enabling indirect
cancellation of caplets via their workers. The issue is not resolved
since indirect cancellation of caplets via their caplet dependencies
still does not work as intended. A new, failing regression test has been
added for this specific case.

The revealed bug is #2021, which we believed to be fixed by #2092.
Rather than fixing the bug, that PR concealed it by always creating a
new incarnation of `eval` formula workers, even if they already existed.
The regression test for #2021 has been marked as failing, and we will
have to find a different solution for it.
rekmarks added a commit that referenced this pull request Mar 9, 2024
Progresses: #2086

Synchronizes the host's `makeUnconfined()` per #2086. Refactoring `daemon.js` in support of this goal fixed one bug while revealing another.

In particular, #2074 is progressed by enabling indirect cancellation of caplets via their workers. The issue is not resolved since indirect cancellation of caplets via their caplet dependencies still does not work as intended. A new, failing regression test has been added for this specific case.

The revealed bug is #2021, which we believed to be fixed by #2092. Rather than fixing the bug, that PR concealed it by always creating a new incarnation of `eval` formula workers, even if they already existed. The regression test for #2021 has been marked as failing, and we will have to find a different solution for it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

daemon: eval-mediated worker names do not resolve
3 participants