This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Simplify cancellation public API
ClosedPublic

Authored by sammccall on Sep 12 2018, 10:55 AM.

Details

Summary

Task is no longer exposed:

  • task cancellation is hidden as a std::function
  • task creation returns the new context directly
  • checking is via free function only, with no way to avoid the context lookup

The implementation is essentially the same, but a bit terser as it's hidden.

isCancelled() is now safe to use outside any task (it returns false).
This will leave us free to sprinkle cancellation in e.g. TUScheduler without
needing elaborate test setup, and lets callers that don't cancel "just work".

Updated the docs to describe the new expected use pattern.
One thing I noticed: there's nothing async-specific about the cancellation.
Async tasks can be cancelled from any thread (typically the one that created
them), sync tasks can be cancelled from any *other* thread in the same way.
So the docs now refer to "long-running" tasks instead of async ones.

Updated usage in code complete, without any structural changes.
I didn't update all the names of the helpers in ClangdLSPServer (these will
likely be moved to JSONRPCDispatcher anyway).

Diff Detail

Event Timeline

sammccall created this revision.Sep 12 2018, 10:55 AM

Fix includes, formatting tweak.

Seems a lot cleaner now, thanks!

Do you plan to have other changes like moving control to JSONRPCDispatcher and recording timings for analysis on this patch? If not maybe we can add some fixme's so that we won't forget. Also the somewhat "caching" of cancellation token from previous implementation might still be useful in future if we really face "crowded" contexts and frequent cancellation checks, so maybe keep some notes about it?

clangd/Cancellation.h
21

s/TaskCancelledError/CancelledError

21

Maybe also mention propagating context into long-running work. runAsync does that implicitly and not sure if there will be other use cases that doesn't include it, but if there might be it would be nice to point it out as well.

sammccall updated this revision to Diff 165158.Sep 12 2018, 2:44 PM
sammccall marked 2 inline comments as done.

Address comments by adding comments!

Seems a lot cleaner now, thanks!

Do you plan to have other changes like moving control to JSONRPCDispatcher and recording timings for analysis on this patch?

D52004 is the JSONRPCDispatcher change.
Added a FIXME to the file comment about timings, hope to get to it soon.

If not maybe we can add some fixme's so that we won't forget. Also the somewhat "caching" of cancellation token from previous implementation might still be useful in future if we really face "crowded" contexts and frequent cancellation checks, so maybe keep some notes about it?

I think the best plan for frequent cancellation checks is "don't do that" ;-) The situation is that you're checking for cancellation to save CPU, but the thing you're spending the CPU on is checking for cancellation... I don't think the fix is to make cancellation checking cheaper! (Obviously cheaper is always better, but it would make the API worse here)
So i left a note on isCancelled(). We can revisit if we find cases where context traversal is really hurting us.

clangd/Cancellation.h
21

Yeah, added this to the caveats.
This shouldn't be a problem in practice (at least for pure open-source clangd): LLVM doesn't have a lot of multithreading utilities so we mostly define our own, and those are context aware.

but the thing you're spending the CPU on is checking for cancellation...

Unless checking for cancellation is really cheap (which is doable, I think).
We should probably hit some of those in practice before doing something, though.

clangd/Cancellation.h
71

Allowing fast checking for cancellation seem important.
E.g. if we want to cancel in the middle of parsing, we'll probably be faced with sema callbacks, frequency of which we don't control, so we'd better design something that's fast to check.

I see two ways to deal with this:

  • Keep the current design, but cache last access to context key.
  • Add an API to get something that can quickly check for cancellation, i.e. something that would hold a reference to resolved context key.

Both could be added later, though, and I don't have any data, it's just my intuition.

Will let Kadir finish the review, consider lgtm'ed from me ;-)

This revision is now accepted and ready to land.Sep 13 2018, 12:57 AM

but the thing you're spending the CPU on is checking for cancellation...

Unless checking for cancellation is really cheap (which is doable, I think).
We should probably hit some of those in practice before doing something, though.

My point is that if a few pointer traversals/comparisons is significant, then the loop is so fast that checking for cancellations doesn't make sense anyway - we're aiming to save 10s of ms, not a few hundred nanos.

Like you say, maybe there are important cases where we can't tell that we're calling frequently, or can't easily avoid that. We don't have any yet though, so optimizing this seems premature.

clangd/Cancellation.h
71

Yeah, we have a few options if we get there here.
Maybe the simplest is

if (LLVM_UNLIKELY(++Counter % 1000 == 0))
 if (isCancelled())
  return;

(obviously this is limited, but it depends what problem we're facing)

This revision was automatically updated to reflect the committed changes.