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

Cycle recovery #6

Closed
nikomatsakis opened this issue Oct 1, 2018 · 4 comments
Closed

Cycle recovery #6

nikomatsakis opened this issue Oct 1, 2018 · 4 comments
Labels
rfc Active discussion about a possible future feature

Comments

@nikomatsakis
Copy link
Member

So handling cycles has traditionally been a thorny issue. Right now the code panics in the event of a cycle. We probably want this to stay an option, but it's probably not the best choice long term — particularly since it often happens that the order of queries is determined by user input (and hence that cycles are somewhat out of our control).

This issue is focused on the case where a cycle represents an error. I'm going to open a sister issue to focus on the case of "succesful cyclic computations".

One challenge with cycles is that they can easily lead to non-determinism. For example, imagine that we return a Result — if that is not faithfully propagated up the entire cycle, that implies that foo.query().get(K) could sometimes yield Ok (the root call) and other times yield Err (the cyclic call). This somewhat invalidates all the memoized results in the middle, since they are dependent upon the Err result. But if we had executed things in another order, things might have turned out differently. This is undesirable. It's particularly bad if we cache such results.

@nikomatsakis
Copy link
Member Author

One option is to walk the stack whenever we encounter a cycle and mark all the participants in some way. We could then avoid caching their return values. Or, even more extreme, we could ignore their results altogether and just propagate back dummy return values. (That is, the query can finish executing as normal, but its return value is ignoring, and some kind of default or cycle value is propagated back. Obviously this is related to the idea of terminating unwanted queries early.)

To just skip caching would be relatively easy. Each active query would carry some AtomicBool and we'd flip the flags to true when we detect a cycle. When the query finished, we'd propagate back this result so that the storage cells can know what to do.

If we wanted to propagate back errors, we have to extend the system slightly to know what value to return. You could imagine this working like so: in order to recover from a cycle, a query must have a defined return value like Result<T, CycleError>. If all the participants in the cycle have such a return value, and a cycle occurs, we'll force them all to be Err. You could also imagine that — in order to recover from a cycle — the query's return value must implement some trait (like Default, but probably not Default), or that the query must write a "cycle hook" defining what return value to produce.

I'm a bit concerned that having to "opt in" to cycle handling will error prone, though. Perhaps just "no cache" is a better starting point.

@matklad
Copy link
Member

matklad commented Oct 1, 2018

Here's the bit of IntelliJ that deals with this issue: https://github.com/JetBrains/intellij-community/blob/9d69a9c4245956ce063b20b1bf99165422dfefc2/platform/util/src/com/intellij/openapi/util/RecursionManager.java#L17-L37 (RecursionManager is what you get when you mix FP and OOP)

@nikomatsakis nikomatsakis added the rfc Active discussion about a possible future feature label Oct 1, 2018
@nikomatsakis nikomatsakis added this to the MVP milestone Oct 1, 2018
Marwes added a commit to Marwes/salsa that referenced this issue Feb 3, 2019
Quickest POC I could create to get some potentially cyclic queries to
not panic and instead return a result I could act on. (gluon's module
importing need to error on cycles).

```
// Causes `db.query()` to actually return `Result<V, CycleError>`
fn query(&self, key: K, key2: K2) -> V;
```

A proper implementation of this would likely return
`Result<V, CycleError<(K, K2)>>` or maybe larger changes are needed.

cc salsa-rs#6
bors bot added a commit to rust-lang/rust-analyzer that referenced this issue Feb 24, 2019
892: Type aliases r=matklad a=flodiebold

This implements type aliases (i.e. `type` definitions).

There's just one snag: handling recursion. E.g. `type Foo = Foo` makes type inference panic with a query cycle. I think the best way to handle this would be if Salsa provided the ability to catch cycle errors? It seems that there's some work underway to support this [here](salsa-rs/salsa#6) and [here](salsa-rs/salsa#147). Should we wait for this? I don't see a good way to handle this without help from Salsa.

Co-authored-by: Florian Diebold <flodiebold@gmail.com>
Marwes added a commit to Marwes/salsa that referenced this issue May 4, 2019
Quickest POC I could create to get some potentially cyclic queries to
not panic and instead return a result I could act on. (gluon's module
importing need to error on cycles).

```
// Causes `db.query()` to actually return `Result<V, CycleError>`
fn query(&self, key: K, key2: K2) -> V;
```

A proper implementation of this would likely return
`Result<V, CycleError<(K, K2)>>` or maybe larger changes are needed.

cc salsa-rs#6
Marwes added a commit to Marwes/salsa that referenced this issue Jul 21, 2019
Quickest POC I could create to get some potentially cyclic queries to
not panic and instead return a result I could act on. (gluon's module
importing need to error on cycles).

```
// Causes `db.query()` to actually return `Result<V, CycleError>`
fn query(&self, key: K, key2: K2) -> V;
```

A proper implementation of this would likely return
`Result<V, CycleError<(K, K2)>>` or maybe larger changes are needed.

cc salsa-rs#6
Marwes added a commit to Marwes/salsa that referenced this issue Jul 21, 2019
Quickest POC I could create to get some potentially cyclic queries to
not panic and instead return a result I could act on. (gluon's module
importing need to error on cycles).

```
// Causes `db.query()` to actually return `Result<V, CycleError>`
fn query(&self, key: K, key2: K2) -> V;
```

A proper implementation of this would likely return
`Result<V, CycleError<(K, K2)>>` or maybe larger changes are needed.

cc salsa-rs#6
Marwes added a commit to Marwes/salsa that referenced this issue Jul 21, 2019
Quickest POC I could create to get some potentially cyclic queries to
not panic and instead return a result I could act on. (gluon's module
importing need to error on cycles).

```
// Causes `db.query()` to actually return `Result<V, CycleError>`
fn query(&self, key: K, key2: K2) -> V;
```

A proper implementation of this would likely return
`Result<V, CycleError<(K, K2)>>` or maybe larger changes are needed.

cc salsa-rs#6
nikomatsakis added a commit to nikomatsakis/salsa that referenced this issue Aug 15, 2019
- adopt the new `Durability` API proposed in [RFC salsa-rs#6]
- Adopt `AtomicU64` for `runtimeId` (salsa-rs#182)
- use `ptr::eq` and `ptr::hash` for readability
- upgrade parking lot
- remove needless clone

[RFC salsa-rs#6]: salsa-rs/salsa-rfcs#6
Marwes added a commit to Marwes/salsa that referenced this issue Aug 16, 2019
Quickest POC I could create to get some potentially cyclic queries to
not panic and instead return a result I could act on. (gluon's module
importing need to error on cycles).

```
// Causes `db.query()` to actually return `Result<V, CycleError>`
fn query(&self, key: K, key2: K2) -> V;
```

A proper implementation of this would likely return
`Result<V, CycleError<(K, K2)>>` or maybe larger changes are needed.

cc salsa-rs#6
Marwes added a commit to Marwes/salsa that referenced this issue Aug 16, 2019
Quickest POC I could create to get some potentially cyclic queries to
not panic and instead return a result I could act on. (gluon's module
importing need to error on cycles).

```
// Causes `db.query()` to actually return `Result<V, CycleError>`
fn query(&self, key: K, key2: K2) -> V;
```

A proper implementation of this would likely return
`Result<V, CycleError<(K, K2)>>` or maybe larger changes are needed.

cc salsa-rs#6
@lpil
Copy link

lpil commented Mar 5, 2020

Is there an approach to this we can use today? Thank you

@nikomatsakis
Copy link
Member Author

We've got this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfc Active discussion about a possible future feature
Projects
None yet
Development

No branches or pull requests

3 participants