[PATCH] D50502: [clangd] Initial cancellation mechanism for LSP requests.
Ilya Biryukov via Phabricator via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 14 07:47:22 PDT 2018
ilya-biryukov added a comment.
Sorry for missing this one. The biggest concern that I have is about the thread-safety of the API, it's too easy to misuse from multiple threads at this point.
We should define thread-safety guarantees for `TaskHandle` and `CancellationToken` and make sure they're not easily broken. The suggested way is to make both move-only types.
For `CancellationToken` we might need a non-thread-safe copy operation, though. But maybe make it a separate method (`clone`?) with a clear statement that it's not thread-safe?
Other than that just NITs.
================
Comment at: clangd/Cancellation.cpp:17
+namespace {
+static Key<std::shared_ptr<std::atomic<bool>>> CancellationTokenKey;
+} // namespace
----------------
Having a `shared_ptr` key in the Context can cause data races (e.g. if we copy it concurrently from multiple threads).
I suggest we make `CancellationToken` move-only (i.e. disallow copies) and return `const CancellationToken&` when getting it from the context.
================
Comment at: clangd/Cancellation.cpp:34
+llvm::Error CancellationHandler::getCancellationError() {
+ return llvm::make_error<TaskCancelledError>();
+}
----------------
I'm not sure if this function buys us much in terms of clarity, maybe remove it and inline everywhere?
================
Comment at: clangd/Cancellation.h:9
+//===----------------------------------------------------------------------===//
+// CancellationToken mechanism for async threads. The caller can generate a
+// TaskHandle for cancellable tasks, then bind that handle to current context
----------------
The comments seems to mention all the important bits, but maybe we could separate different concerns more clearly based on how the code should be used?
E.g. here's a rough draft to illustrate the idea:
```
Cancellation mechanism for async tasks. Roughly all the clients of this code can be classified into three categories:
1. The code that creates and schedules async tasks, e.g. TUScheduler.
2. The callers of the async method that can cancel some of the running tasks, e.g. `ClangdLSPServer`
3. The code running inside the async task itself, i.e. code completion or find definition implementation that run clang, etc.
For (1), the guideline is to accept a callback for the result of async operation and return a `TaskHandle` to allow cancelling the request.
TaskHandle someAsyncMethod(function<void(llvm::Expected<ResultType>)> Callback);
The callers of async methods (2) can issue cancellations and should be prepared to handle `TaskCancelledError` result:
TaskHandle H = someAsyncMethod([](llvm::Expected<ResultType> R) {
if (/* code to check for TaskCancelledError */)
llvm::errs() << "Task got cancelled";
else
llvm::errs() << "The result is: " << *R;
}
});
sleep(5);
H.cancel();
The worker code itself (3) should check for cancellations using `CancellationToken` that can be retrieved via `CancellationToken::isCancelled()`.
void codeComplete() {
const CancellationToken& CT = CancellationToken::isCancelled();
if (CT.isCancelled())
return llvm::makeError<TaskCancelledError>();
runSema();
if (CT.isCancelled())
return llvm::makeError<TaskCancelledError>();
queryIndex();
return results;
}
If the operation was cancelled before it could run to completion, it should propagate the TaskCancelledError as a result.
```
================
Comment at: clangd/Cancellation.h:60
+
+class CancellationToken {
+private:
----------------
We're missing docs on this and the other classes. I suggest a small doc describing what they're used for, e.g. `used for checking if the operation was cancelled` and `used to signal the operation should be cancelled`, etc.
Also noting the threading-safety guarantees is a probably a good idea.
================
Comment at: clangd/Cancellation.h:61
+class CancellationToken {
+private:
+ std::shared_ptr<const std::atomic<bool>> Token;
----------------
NIT: move members to the end of the class to be consistent with the rest of clangd.
================
Comment at: clangd/Cancellation.h:67
+ operator bool() const { return isCancelled(); }
+ CancellationToken(const std::shared_ptr<const std::atomic<bool>> Token)
+ : Token(Token) {}
----------------
Can we make this ctor private?
================
Comment at: clangd/Cancellation.h:71
+
+class TaskHandle {
+public:
----------------
I wonder if we should make `TaskHandle` move-only?
The reasoning is that is can be easily used from multiple threads (since it's used by code dealing with async tasks anyway) and copying it concurrently is a data race.
On the other hand, calling `cancel()` is perfectly thread-safe and shouldn't cause any problems.
================
Comment at: clangd/Cancellation.h:74
+ void cancel();
+ static TaskHandle createCancellableTaskHandle();
+ friend class CancellationHandler;
----------------
NIT: maybe use a shorter name, e.g. `TaskHandle::create`?
================
Comment at: clangd/Cancellation.h:82
+
+class CancellationHandler {
+public:
----------------
Maybe remove `CancellationHandler` and put all its functions into the clangd namespace? It's somewhat easily confused with `CancellationToken`.
We could declare some function friends if we need to
================
Comment at: clangd/Cancellation.h:89
+
+class TaskCancelledError : public llvm::ErrorInfo<TaskCancelledError> {
+public:
----------------
NIT: maybe use a shorter name: `CancelledError`?
================
Comment at: clangd/ClangdLSPServer.cpp:355
+ Error Uncaught =
+ handleErrors(List.takeError(), [](const TaskCancelledError &) {
+ replyError(ErrorCode::RequestCancelled,
----------------
I expect the pattern `if cancelled -> reply with ErrorCode::RequestCancelled else reply with ErrorCode::InvalidParams` to be pretty common when we add cancellation to more operations.
Maybe add a small helper that does that? E.g.
```
/// Replies to a request with an error, passing proper error codes to the LSP client.
/// Properly handles cancellations.
void replyError(Error E);
```
================
Comment at: clangd/ClangdLSPServer.cpp:627
+void ClangdLSPServer::onCancelRequest(CancelParams &Params) {
+ std::lock_guard<std::mutex> _(TaskHandlesMutex);
+ const auto &it = TaskHandles.find(Params.ID);
----------------
NIT: use `Lock` or `L` for consistencty with the rest of clangd. it's somewhat easy to misread `std::lock_guard<std::mutex> _(Mut)` as `std::lock_guard<std::mutex> (Mut)`
Repository:
rCTE Clang Tools Extra
https://reviews.llvm.org/D50502
More information about the cfe-commits
mailing list