This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Run PreambleThread in async mode behind a flag
ClosedPublic

Authored by kadircet on May 20 2020, 5:24 AM.

Details

Summary

Depends on D80198.

This patch implies ASTs might be built with stale preambles without
blocking for a fresh one. It also drops any guarantees on every preamble
version being built. In case of multiple preamble build requests, in
addition to being debounced.

Any preamble requested with a WantDiags::Yes will always be built, this
is ensured by blocking enqueueing of any subsequent reqest.

AST worker will still block for initial preamble to reduce duplicate
work.

Diff Detail

Event Timeline

kadircet created this revision.May 20 2020, 5:24 AM
Herald added a project: Restricted Project. · View Herald TranscriptMay 20 2020, 5:24 AM
kadircet edited the summary of this revision. (Show Details)May 20 2020, 7:27 AM
kadircet added a reviewer: sammccall.
kadircet marked an inline comment as done.
kadircet added inline comments.
clang-tools-extra/clangd/TUScheduler.cpp
248

This is mostly to make sure tests do not regress, I am not sure if we have any real world uses of it though.

I would love to just drop this and change tests instead. WDYT?

kadircet updated this revision to Diff 266201.May 26 2020, 7:16 AM
kadircet edited the summary of this revision. (Show Details)
  • Rebase and update comments on block until idle.

Sorry about the delay on this, I said I'd prioritize it... had a slightly odd day.

Haven't reviewed the unit test yet (mostly a reminder to myself).
Generally looks good...

clang-tools-extra/clangd/ClangdServer.h
156

Group with StorePreamblesInMemory.

This comment doesn't make much sense either - PreamblePeer describe's the thread's relation to the main ASTWorker thread, but readers here won't know what it is. What about:
Reuse even stale preambles, and rebuild them in the background.
This improves latency at the cost of accuracy.

clang-tools-extra/clangd/TUScheduler.cpp
244

First sentence makes sense, code makes sense, I can't parse the second sentence at all.
Just "block until it's built"?

Any maybe something about why we won't deadlock, but tests are enough I guess.

292

add comment // will never build one, or so

543–550

we're passing around enough loose booleans we should consider having TUScheduler hold its Options struct, and pass it here by refrerence...

654

"ready, as patching..."

721

hmm, we're notifying the ASTPeer, and then setting builtfirst which is being waited on... by astpeer.
This suggests to me the AST thread should own the notification, not the preamble thread.
And in fact it already has a notification for the first preamble being built! why can't we use that one?

1131

I think this would be clearer as a loop... and maybe more correct.

1143

um... isn't that allowed?
If new traffic means the queue doesn't go idle, I think we're supposed to return false, rather than crash.

clang-tools-extra/clangd/TUScheduler.h
195

nit: break before second sentence?

clang-tools-extra/clangd/tool/ClangdMain.cpp
421

I'd drop "builds" at least from the flag name, possibly througout

423

This isn't very descriptive. I'd use some variation of the comment suggested for the ClangdServer::Options

sammccall added inline comments.May 28 2020, 1:24 AM
clang-tools-extra/clangd/TUScheduler.cpp
1135

I'd consider pulling out an IsIdle lambda (checking for all 3 conditions) and using it both here and at the bottom.

PreambleRequests will never be (solely) full here, and Requests will always be empty at the bottom, but it's harmless and I think easier to read.

1139

Maybe explicitly: "This might create new PreambleRequests for the ASTWorker."

1143

OK, I somehowe forgot that ASTWorker isn't threadsafe so we only have the current thread and the worker thread to worry about.

Suggest a slightly expanded message like: No new normal tasks can be scheduled concurrently with blockUntilIdle(): ASTWorker isn't threadsafe

clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
101

Consider inlining this class until we have a second user.
It's not clear that adapting between lambda and an interface clarifies the test enough to justify the indirection.

101

capturePreamble isn't the right name for this function, unlike captureDiags it doesn't actually send the preamble anywhere.

453

As you've pointed out, Yes isn't used in production. So if the behaviour of Auto has changed we should test that rather than stop using it.

My understanding of the change:

  1. we've asserted that every update yields diagnostics
  2. this was true because we were calling runWithAST, this forced an AST build at each snapshot, and we'd publish the diagnostics
  3. now we only publish diagnostics once the version has been through the preamble thread and back
  4. these requests get coalesced in the preamble thread if the AST worker is pushing them faster than the preamble worker is servicing them
  5. so we no longer publish diagnostics for every version even if we have a suitable AST

Is this right?
If so, I'd assert that TotalUpdates is at least FilesCount and at most FilesCount * UpdatesPerFile, and also that the latest UpdateI for diags (for any file) is equal to UpdatesPerFile - 1.
And maybe add a brief explanation (we drop diagnostics for some snapshots as they get coalesced in the preamble worker).

1000

This seems like a dubious use of atomic (lack of synchronization between accesses) - there's actually only one thread here, right?

It would be much clearer IMO to just test the passed-in Version directly.

1021

nit: spell out "blocking" explicitly

1021

if you expose preambleVersion() from ParsedAST, you could assert on it directly. I think this would be much clearer than the stuff with PreambleBuildCount.

(I don't see any reason not to expose preamble itself, but this probably isn't a good enough reason to do it)

kadircet marked 22 inline comments as done.May 28 2020, 4:37 AM
kadircet added inline comments.
clang-tools-extra/clangd/TUScheduler.cpp
721

umm, it is a little bit more complicated.

The above call only inserts the "update request" onto ASTPeer's queue and doesn't update the preamble inside ASTPeer. This enables ASTPeer to access LatestPreamble without holding the lock, as it is the only writer. We can change that, update the LatestPreamble while queueing the request.

1131

as discussed offline, loop version isn't more clear keeping the straight version.

clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
453

Is this right?

yes. in addition to those, previously Auto promised a diagnostics release if it was followed by a read(that's how this test used to work). we no longer guarantee that for the reasons you've mentioned.

What about keeping WantDiagnostics::Yes at all? I believe it should be a separate patch none the less, but I would be happy with silently converting Yes to Auto. I would expect this to only break editors not using Auto at all and always forcing diags or not requesting them at all.

kadircet updated this revision to Diff 266814.May 28 2020, 4:37 AM
kadircet marked 2 inline comments as done.
  • Address comments
sammccall accepted this revision.May 29 2020, 2:29 AM
sammccall added inline comments.
clang-tools-extra/clangd/TUScheduler.cpp
654

This isn't the natural place to block, rather where the preamble would be consumed.
But that's too late, we'd be running on the worker thread with the PreambleTask scheduled and so we'd deadlock.
This needs a comment :-)

721

As discussed, the relationship between the two is complicated.
At least we should move the second into ASTPeer to reduce the surface area between the two.

As a followup, we can "explode" the existing notification into CV + Mutex + bool.
The bool can naturally become the "present" bit on LatestPreamble if it turns into an Optional<shared_ptr<>> - we must document none vs null!
The Mutex already exists.

Then we don't need the second notification: we just wait on !preambletasks.empty() || LatestPreamble.hasValue()

This revision is now accepted and ready to land.May 29 2020, 2:29 AM
kadircet updated this revision to Diff 267156.May 29 2020, 2:58 AM
  • Move notification from PreambleThread to ASTWorker
kadircet updated this revision to Diff 267157.May 29 2020, 3:03 AM
kadircet marked 3 inline comments as done.
  • Add comment explaining why we block on ASTWorker::update rather than consumers of a preamble
This revision was automatically updated to reflect the committed changes.

Hi!

Shortly after this patch went in, we started to see intermittent failures with the unittest TUSchedulerTests.ManyUpdates. The build server (Windows) we're running it on is quite powerful and our own hand-run tests only take about 400 ms, so we don't really understand what could be happening. Any help would be appreciated.

Here is an excerpt from the log:

Note: Google Test filter = TUSchedulerTests.ManyUpdates

[==========] Running 1 test from 1 test case.

[----------] Global test environment set-up.

[----------] 1 test from TUSchedulerTests

[ RUN ] TUSchedulerTests.ManyUpdates

C:\j\w\...\clang-tools-extra\clangd\unittests\TUSchedulerTests.cpp(508): error: Value of: S.blockUntilIdle(timeoutSeconds(10))

Actual: false

Expected: true

[ FAILED ] TUSchedulerTests.ManyUpdates (10576 ms)

[----------] 1 test from TUSchedulerTests (10576 ms total)