This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Rebuild dependent files when a header is saved.
ClosedPublic

Authored by sammccall on Apr 9 2020, 6:27 PM.

Details

Summary

Caveats:

  • only works when the header is changed in the editor and the editor provides the notification
  • we revalidate preambles for all open files (stat all their headers) rather than taking advantage of the fact that we know which file changed. This is much simpler!

Fixes https://github.com/clangd/clangd/issues/107

Diff Detail

Event Timeline

sammccall created this revision.Apr 9 2020, 6:27 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 9 2020, 6:27 PM

should we also have a unittest for checking ast caching works as expected?

ClangdServer::getUsedBytesPerFile should allow us to do that.

We can set cache size to one, send 3 updates in respective order to foo, bar and baz, we record usedbytesperfile.
Then we perform adddocument with same contents to foo and bar and check usedbytesperfile are still the same?

clang-tools-extra/clangd/ClangdLSPServer.cpp
576

spec also specifies this as property name (optional): textDocumentSync.didSave near didSave notification (in addition to defining it as save in the struct). :(

it also says textDocumentSync can also be a number for backward compat reasons, but doesn't say when the struct was added. I hope it is not recent and we don't end up breaking clients with our capab response :/

no action needed just complaining ...

1588

nit: while here, braces and early exit.

for (..) {
  if(!Filter())
    continue;
  if(auto draft...) server->AddDoc();
}
sammccall marked 3 inline comments as done.Apr 11 2020, 2:29 AM

should we also have a unittest for checking ast caching works as expected?

TUSchedulerTests has NoopOnEmptyChanges which I think tests exactly this, and EvictedAST which checks it's sensitive to the cache.

I don't think testing it again indirectly at this level makes is a good tradeoff, especially if we need subtle assertions via the memory usage.

clang-tools-extra/clangd/ClangdLSPServer.cpp
576

spec also specifies this as property name (optional): textDocumentSync.didSave

Good catch. VSCode and every other impl I could find implement save though, so I think that's just a spec bug. Sent https://github.com/microsoft/language-server-protocol/pull/958

(We could set both here, but I don't think it's a good idea to muddy the water)

it also says textDocumentSync can also be a number for backward compat reasons, but doesn't say when the struct was added. I hope it is not recent and we don't end up breaking clients with our capab response :/

yeah. I hadn't really internalized that all the nice compatibility hacks in the spec are for servers, and don't help much with clients.
I think this is pretty old though, and there's not much we can do about it anyway.

no action needed just complaining ...

Ack, and agreed :-)

sammccall updated this revision to Diff 256756.Apr 11 2020, 2:30 AM
sammccall marked an inline comment as done.

Address review comments

kadircet accepted this revision.Apr 11 2020, 5:53 AM

Thanks, LGTM!

should we also have a unittest for checking ast caching works as expected?

TUSchedulerTests has NoopOnEmptyChanges which I think tests exactly this, and EvictedAST which checks it's sensitive to the cache.

Right, but none of them checks exactly this behaviour. The NoopOnEmptyChanges test only ensures we don't publish diagnostics again
and EvictedAST test actually asserts on the opposite, it checks the last updated file is "always" cached. Maybe extending EvictedAST
test with a "noop update" might be a better fit. Up to you though.

I don't think testing it again indirectly at this level makes is a good tradeoff, especially if we need subtle assertions via the memory usage.

To be clear I wasn't suggesting a test in ClangdLSPServer layer, but rather on ClangdServer layer. but as mentioned above, tuscheduler might
be a better level.

This revision is now accepted and ready to land.Apr 11 2020, 5:53 AM

Thanks, LGTM!

should we also have a unittest for checking ast caching works as expected?

TUSchedulerTests has NoopOnEmptyChanges which I think tests exactly this, and EvictedAST which checks it's sensitive to the cache.

Right, but none of them checks exactly this behaviour. The NoopOnEmptyChanges test only ensures we don't publish diagnostics again
and EvictedAST test actually asserts on the opposite, it checks the last updated file is "always" cached. Maybe extending EvictedAST
test with a "noop update" might be a better fit. Up to you though.

OK, I misunderstood what we're testing, please tell me if I got it right this time :-)

Plausible bad behavior: we send a no-op change to a relatively inactive file. TUScheduler builds an AST (wasting CPU) and caches it (wasting CPU + latency by displacing a useful entry).

We want to prevent either of those from happening. Caching is indirectly observable, building an AST isn't particularly observable. If building an AST *was* observable, then we have an alternate (IMO more meaningful) way of measuring the cache effects: try to use read the maybe-evicted AST and see if it rebuilds.
So I think it would be nicer to structure a test around rebuilds.

A couple of options I can think of:

  • add an API like unsigned TUScheduler::buildCount(PathRef), exposing a counter only used for testing? (Or extend the memory usage API to provide this info too)
  • register a tracer for the test, and count BuildAST events (not too fragile if we assert there are exactly N > 0 of them). This seems hacky, technique/tracer may be reusable for other tests. Not sure whether that's a good thing.

(Unless you object, I'll land this patch once I have a test out for review so we're sure the existing behavior this patch depends on is there)

OK, I misunderstood what we're testing, please tell me if I got it right this time :-)

Plausible bad behavior: we send a no-op change to a relatively inactive file. TUScheduler builds an AST (wasting CPU) and caches it (wasting CPU + latency by displacing a useful entry).

We want to prevent either of those from happening. Caching is indirectly observable, building an AST isn't particularly observable.

Building an AST is also observable(at least in the absence of read requests), as we always generate diagnostics afterwards and that's what TUSchedulerTests.NoopOnEmptyChange actually asserts(that we don't build ASTs on noop changes).
I was suggesting to test the "cache" behaviour separately from building an ast. I know that's redundant today, as we never cache an ast if we haven't build any, but there are no tests ensuring it stays that way.

If building an AST *was* observable, then we have an alternate (IMO more meaningful) way of measuring the cache effects: try to use read the maybe-evicted AST and see if it rebuilds.
So I think it would be nicer to structure a test around rebuilds.

A couple of options I can think of:

  • add an API like unsigned TUScheduler::buildCount(PathRef), exposing a counter only used for testing? (Or extend the memory usage API to provide this info too)
  • register a tracer for the test, and count BuildAST events (not too fragile if we assert there are exactly N > 0 of them). This seems hacky, technique/tracer may be reusable for other tests. Not sure whether that's a good thing.

I suppose that could be a more robust way of checking if an AST was build rather than waiting for diags to be released. I believe the former would be enough(preferably just extending the existing API to not create more test-only endpoints).

(Unless you object, I'll land this patch once I have a test out for review so we're sure the existing behavior this patch depends on is there)

I totally agree, please do so.

nridge added a subscriber: nridge.Apr 11 2020, 6:06 PM
This revision was automatically updated to reflect the committed changes.