Create a ThreadsafeFS in the DraftStore that overlays the dirty file contents over another ThreadsafeFS.
This provides a nice thread-safe interface for using dirty file contents throughout the codebase, for example cross file refactoring.
Creating a Filesystem view will overlay a snapshot of the current contents, so if the draft store is updated while the view is being used, it will contain stale contents.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Refactor RefCntString out of DraftStore class
Remove MTime from Draft, other users of DraftStore don't need to see it.
Sorry for the long delay here. I've gotten myself buried in too many different things.
Overall the idea of exposing drafts as an overlay FS is definitely growing on me - it makes it easier and clearer to control which feature is seeing which version of the files.
At a high level my main requests are:
- we should move maintenance of the drafts from ClangdLSPServer into ClangdServer. (This should maybe have been done a while ago, but this change makes it more complicated and therefore pressing)
- we should simplify the implementation as much as possible, and lean on existing infrastructure - there's a lot of custom pieces here like RefCntString and DraftStoreFileSystem that seem like small optimizations over existing types
(I might take a crack at moving DraftStore by itself, as there probably are issues and I want to understand what they are...)
clang-tools-extra/clangd/ClangdServer.h | ||
---|---|---|
176–181 | Passing in FS + DirtyFS doesn't seem quite right, because we also pass in all the mutations that create the differences. It seem rather that *ClangdServer* should maintain the overlay containing the dirty buffers, and expose it (for use in the few places that ClangdLSPServer currently uses DraftStore). (the fact that DraftStore isn't accessible from ClangdServer did also come up when trying to split the *Server classes into Modules, so this seems like a helpful direction from that perspective too) | |
clang-tools-extra/clangd/DraftStore.cpp | ||
138 | These classes need comments describing why they exist :-) | |
141 | Is there any reason to think we need this micro-optimization? It's quite invasive. | |
206 | can't we use InMemoryFileSystem for this? | |
280 | doing IO in view() doesn't seem obviously OK. what's the practical consequence of the overlay's inodes not matching that of the underlying FS? (It seems reasonable to say that the files always have different identity, and may/may not have the same content) | |
clang-tools-extra/clangd/DraftStore.h | ||
25 | shared_ptr<const string> seems like a cheap alternative here. However if the point is ultimately to obtain MemoryBuffers that we can hand out with the underlying data being refcounted, can we do that directly? class SharedBuffer : public MemoryBuffer { std::shared_ptr<const std::string> Data; public: Shared(StringRef Name, std::string Data); Shared(const RefcountBuffer &); // copyable }; | |
31 | providing non-explicit string conversion operators is IME very likely to cause ambiguous overload errors when using this in variaous places :-( | |
81 | avoid assert in headers for ODR reasons (ooh, we have a couple of violations i should clean up) |
clang-tools-extra/clangd/ClangdServer.h | ||
---|---|---|
176–181 | I was never a fan of this tbh. But moving DraftStore would result in this being too big, | |
clang-tools-extra/clangd/DraftStore.cpp | ||
141 | Its basically copied from llvm/support/MemoryBuffer.cpp | |
206 | InMemoryFileSystem Would work in theory. However using that requires building a lot of unnecessary nodes for thing like intermediate directories. | |
280 | It's probably not necessary, but I know preambles use the UniqueID. It may just be safer to create a uniqueID for each item in the DraftStore and leave it at that. |
Address some comments, though I have a feeling @sammccall, I may have to wait until DraftStore has been refactored first before continuing on with this.
D97738 is an attempt at this. It is... sprawly, but it's an overdue cleanup that should fit together well with this patch, I think.
There may be some conflicts but I don't think it'll be terrible actually... I think we should keep going here, it looks really good.
clang-tools-extra/clangd/DraftStore.cpp | ||
---|---|---|
141 | Sure, but this doesn't really answer the question! Can we keep this trivial until we have a reason to optimize it? (Clang typically holds tens of thousands of MemoryBuffers, but we're only likely to have a few hundred of these) | |
206 | Probably, but it would keep this code simpler, supports directory iteration and status (used in code completion), and I'm fairly confident its path handling is solid. It's certainly possible to add this optimization later if it seems important, but in this patch I think it distracts from the design questions. | |
280 | The biggest risks I can see with this approach:
So I think it's as good as we can expect. | |
clang-tools-extra/clangd/DraftStore.h | ||
49 | having DraftStore sit on top of TFS seems a bit inside-out, giving it bigger scope than necessary. What about giving DraftStore an asVFS() method that returns an in-memory filesystem? then separately we can have a separate DirtyFS : TFS, that has a DraftStore& and a TFS &Base and implements viewImpl() on top of their public interfaces. Having the dependency in that direction seems more natural to me. | |
86 | alternatively you can define whichever internal struct you like, like struct FileData { std::string Filename; std::string Contents; // and MTime and UID } and store DenseMap<StringRef, shared_ptr<FileData>> (with the keys pointing into the values). You can still hand out shared_ptr<const std::string> using the aliasing constructor of shared_ptr. The advantages of this:
this is a little bit of complexity though, up to you |
clang-tools-extra/clangd/DraftStore.h | ||
---|---|---|
49 | How I had it was a symptom of using the underlying TFS for getting the UniqueID of files. But yes now that's gone this approach works. |
clang-tools-extra/clangd/ClangdServer.h | ||
---|---|---|
415 | Probably needs moving to its own place, but wasn't sure where best to put it. | |
clang-tools-extra/clangd/DraftStore.cpp | ||
280 | Unfortunately in the current implementation, opening a draft will invalidate and preambles that use the underlying file. |
Thanks, LG!
clang-tools-extra/clangd/ClangdServer.h | ||
---|---|---|
415 | I think ClangdServer is fine, but this can be a unique_ptr<ThreadsafeFS> with the class moved to the implementation file | |
clang-tools-extra/clangd/DraftStore.cpp | ||
57 | we don't need try_emplace here, we overwrite the whole value regardless. | |
60 | nit: std::time() (and std::time_t) | |
155 | nit: /*RequiresNullTerminator=*/ |
clang-tools-extra/clangd/ClangdServer.h | ||
---|---|---|
415 | How about privately inheriting from ThreadsafeFS :P | |
clang-tools-extra/clangd/DraftStore.cpp | ||
57 | I changed this because in an older version I wanted to set the UniqueID on the first time the file was added, however now we don't do that (or check the return value for insertion at all) its safe to go back to the operator[] access. | |
280 | @sammccall Any further comments with regard to this. Is calling IO to get modification time in didOpen ok, or just accept opening a file would invalidate a preamble if the DirtyFS was used for preambles. |
clang-tools-extra/clangd/DraftStore.cpp | ||
---|---|---|
280 | There's no urgency in resolving this:
That said, my suspicion is that having ClangdLSPServer stat the file, conditional on being in preambles-from-dirty-fs mode, would be OK. |
Still LG btw!
clang-tools-extra/clangd/ClangdServer.h | ||
---|---|---|
350–351 | Maybe add an explicit comment that this returns nullptr if the file is not open. | |
415 | nit: we don't use const on pointer members, just on the pointee where appropriate. |
Passing in FS + DirtyFS doesn't seem quite right, because we also pass in all the mutations that create the differences.
It seem rather that *ClangdServer* should maintain the overlay containing the dirty buffers, and expose it (for use in the few places that ClangdLSPServer currently uses DraftStore).
(the fact that DraftStore isn't accessible from ClangdServer did also come up when trying to split the *Server classes into Modules, so this seems like a helpful direction from that perspective too)