salsa-rs / salsa

A generic framework for on-demand, incrementalized computation. Inspired by adapton, glimmer, and rustc's query system.
https://salsa-rs.netlify.app/
Apache License 2.0
2.12k stars 150 forks source link

Concurrent queries and cancellation #12

Closed matklad closed 6 years ago

matklad commented 6 years ago

Continuing https://github.com/nikomatsakis/salsa/pull/4#issuecomment-425715114 discussion.

This is what I expecteded to start: Query computation runs async from main input (perhaps we'll use a Future setup or something) When user input arrives, we process it after current query completes.

This is definitely a good start, but, for IDEs, I think we'll need something better eventually. Let me present a couple of important IDE interaction patterns.

Async Annotations

These are the cases when IDE reacts on user's input or external changes. Examples: syntax highlighting, warnings and errors (which all are various text decorations). It's OK if they appear with a noticeable delay after the user types. However, for syntax highlighting and syntax errors specifically, a noticeable delay is noticeably annoying. For this reason, in IntelliJ they are computed in phases: first, lexical analysis is used to apply basic highlighting, then, parsing is done to report syntax errors, then, precise syntax highlighting is done (which uses some semantic information), and, lastly, errors and warnings are computed.

For this case, it's important to be able to apply changes and compute syntax highlighting without waiting for previous semantic analysis results (which might take some time to calculate, especially if we have a mode that computes errors across the whole project, and not only opened files). Failure to do so will be annoying (why I sometimes get syntax errors immediately, and sometimes after a delay?), but most certainly not a deal breaker.

Sync Actions

These are the cases when IDE reacts to the explicit user's request. Examples: indenting cursor after Enter, re-indenting lines on } typed, extend selection. Most of these things need only a syntax tree, but sometimes semantic is useful as well, for example, in postfix-templates case. Postfix template is this thing: user has an expression, whose value is an enum: compute_thing(stuff), user types .match and pressed tab: compute_thing(stuff).match, and gets a match statement with filled variants: match compute_thing(stuff) { Ok(think) => ... }.

For such "sync" actions, latency directly affects the user, so being able to apply changes while a long-running computation on the old state is running, is not simply a QoL improvement, but rather a question of "can we do this feature at all?".

matklad commented 6 years ago

IntelliJ case study

In intelliJ, cancellation is an important feature of the platform. It works like this:

All data is protected by a single RwLock. Only main thread can .write() it, any thread can .read() it. When the user types, main thread grabs .write, applies changes and starts async annotation tasks on the threadpool. Each annotation task grabs the .read lock. If the user types when annotations are in progress, the main thread cancels all the tasks before grabbing the lock. This cancellation ensures that changes are incorporated quickly.

Sync actions operate on the main thread directly, under the write lock.

matklad commented 6 years ago

When thinking about this in the context of salsa, three solutions come to mind:

nikomatsakis commented 6 years ago

See https://github.com/salsa-rs/salsa/pull/48 which offers some potential building blocks. I admit I didn't realize there was so much text on this issue! I'll read and compare.

matklad commented 6 years ago

48 is merged, and it basically implements what I would call "auto cancellation", although on the opt-in bases, which is even cooler! So, closing!