Reimplement std::future and std::promise to workaround TSAN false positives
This commit is contained in:
parent
1f7f48904a
commit
44d158805c
179
include/dap/future.h
Normal file
179
include/dap/future.h
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// Copyright 2019 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef dap_future_h
|
||||||
|
#define dap_future_h
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace dap {
|
||||||
|
|
||||||
|
// internal functionality
|
||||||
|
namespace detail {
|
||||||
|
template <typename T>
|
||||||
|
struct promise_state {
|
||||||
|
T val;
|
||||||
|
std::mutex mutex;
|
||||||
|
std::condition_variable cv;
|
||||||
|
bool hasVal = false;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// forward declaration
|
||||||
|
template <typename T>
|
||||||
|
class promise;
|
||||||
|
|
||||||
|
// future_status is the enumeration returned by future::wait_for and
|
||||||
|
// future::wait_until.
|
||||||
|
enum class future_status {
|
||||||
|
ready,
|
||||||
|
timeout,
|
||||||
|
};
|
||||||
|
|
||||||
|
// future is a minimal reimplementation of std::future, that does not suffer
|
||||||
|
// from TSAN false positives. See:
|
||||||
|
// https://gcc.gnu.org/bugzilla//show_bug.cgi?id=69204
|
||||||
|
template <typename T>
|
||||||
|
class future {
|
||||||
|
public:
|
||||||
|
using State = detail::promise_state<T>;
|
||||||
|
|
||||||
|
// constructors
|
||||||
|
inline future() = default;
|
||||||
|
inline future(future&&) = default;
|
||||||
|
|
||||||
|
// valid() returns true if the future has an internal state.
|
||||||
|
bool valid() const;
|
||||||
|
|
||||||
|
// get() blocks until the future has a valid result, and returns it.
|
||||||
|
// The future must have a valid internal state to call this method.
|
||||||
|
inline T get();
|
||||||
|
|
||||||
|
// wait() blocks until the future has a valid result.
|
||||||
|
// The future must have a valid internal state to call this method.
|
||||||
|
void wait() const;
|
||||||
|
|
||||||
|
// wait_for() blocks until the future has a valid result, or the timeout is
|
||||||
|
// reached.
|
||||||
|
// The future must have a valid internal state to call this method.
|
||||||
|
template <class Rep, class Period>
|
||||||
|
future_status wait_for(
|
||||||
|
const std::chrono::duration<Rep, Period>& timeout) const;
|
||||||
|
|
||||||
|
// wait_until() blocks until the future has a valid result, or the timeout is
|
||||||
|
// reached.
|
||||||
|
// The future must have a valid internal state to call this method.
|
||||||
|
template <class Clock, class Duration>
|
||||||
|
future_status wait_until(
|
||||||
|
const std::chrono::time_point<Clock, Duration>& timeout) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend promise<T>;
|
||||||
|
future(const future&) = delete;
|
||||||
|
inline future(const std::shared_ptr<State>& state);
|
||||||
|
|
||||||
|
std::shared_ptr<State> state = std::make_shared<State>();
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
future<T>::future(const std::shared_ptr<State>& state) : state(state) {}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool future<T>::valid() const {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T future<T>::get() {
|
||||||
|
std::unique_lock<std::mutex> lock(state->mutex);
|
||||||
|
state->cv.wait(lock, [&] { return state->hasVal; });
|
||||||
|
return state->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void future<T>::wait() const {
|
||||||
|
std::unique_lock<std::mutex> lock(state->mutex);
|
||||||
|
state->cv.wait(lock, [&] { return state->hasVal; });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
template <class Rep, class Period>
|
||||||
|
future_status future<T>::wait_for(
|
||||||
|
const std::chrono::duration<Rep, Period>& timeout) const {
|
||||||
|
std::unique_lock<std::mutex> lock(state->mutex);
|
||||||
|
return state->cv.wait_for(lock, timeout, [&] { return state->hasVal; })
|
||||||
|
? future_status::ready
|
||||||
|
: future_status::timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
template <class Clock, class Duration>
|
||||||
|
future_status future<T>::wait_until(
|
||||||
|
const std::chrono::time_point<Clock, Duration>& timeout) const {
|
||||||
|
std::unique_lock<std::mutex> lock(state->mutex);
|
||||||
|
return state->cv.wait_until(lock, timeout, [&] { return state->hasVal; })
|
||||||
|
? future_status::ready
|
||||||
|
: future_status::timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
// promise is a minimal reimplementation of std::promise, that does not suffer
|
||||||
|
// from TSAN false positives. See:
|
||||||
|
// https://gcc.gnu.org/bugzilla//show_bug.cgi?id=69204
|
||||||
|
template <typename T>
|
||||||
|
class promise {
|
||||||
|
public:
|
||||||
|
// constructors
|
||||||
|
inline promise() = default;
|
||||||
|
inline promise(promise&& other) = default;
|
||||||
|
inline promise(const promise& other) = default;
|
||||||
|
|
||||||
|
// set_value() stores value to the shared state.
|
||||||
|
// set_value() must only be called once.
|
||||||
|
inline void set_value(const T& value) const;
|
||||||
|
inline void set_value(T&& value) const;
|
||||||
|
|
||||||
|
// get_future() returns a future sharing this promise's state.
|
||||||
|
future<T> get_future();
|
||||||
|
|
||||||
|
private:
|
||||||
|
using State = detail::promise_state<T>;
|
||||||
|
std::shared_ptr<State> state = std::make_shared<State>();
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
future<T> promise<T>::get_future() {
|
||||||
|
return future<T>(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void promise<T>::set_value(const T& value) const {
|
||||||
|
std::unique_lock<std::mutex> lock(state->mutex);
|
||||||
|
state->val = value;
|
||||||
|
state->hasVal = true;
|
||||||
|
state->cv.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void promise<T>::set_value(T&& value) const {
|
||||||
|
std::unique_lock<std::mutex> lock(state->mutex);
|
||||||
|
state->val = std::move(value);
|
||||||
|
state->hasVal = true;
|
||||||
|
state->cv.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dap
|
||||||
|
|
||||||
|
#endif // dap_future_h
|
@ -15,12 +15,12 @@
|
|||||||
#ifndef dap_session_h
|
#ifndef dap_session_h
|
||||||
#define dap_session_h
|
#define dap_session_h
|
||||||
|
|
||||||
|
#include "future.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
#include "typeinfo.h"
|
#include "typeinfo.h"
|
||||||
#include "typeof.h"
|
#include "typeof.h"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <future>
|
|
||||||
|
|
||||||
namespace dap {
|
namespace dap {
|
||||||
|
|
||||||
@ -158,9 +158,9 @@ class Session {
|
|||||||
inline void registerSentHandler(F&& handler);
|
inline void registerSentHandler(F&& handler);
|
||||||
|
|
||||||
// send() sends the request to the connected endpoint and returns a
|
// send() sends the request to the connected endpoint and returns a
|
||||||
// std::future that is assigned the request response or error.
|
// future that is assigned the request response or error.
|
||||||
template <typename T, typename = IsRequest<T>>
|
template <typename T, typename = IsRequest<T>>
|
||||||
std::future<ResponseOrError<typename T::Response>> send(const T& request);
|
future<ResponseOrError<typename T::Response>> send(const T& request);
|
||||||
|
|
||||||
// send() sends the event to the connected endpoint.
|
// send() sends the event to the connected endpoint.
|
||||||
template <typename T, typename = IsEvent<T>>
|
template <typename T, typename = IsEvent<T>>
|
||||||
@ -248,24 +248,23 @@ void Session::registerSentHandler(F&& handler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename>
|
template <typename T, typename>
|
||||||
std::future<ResponseOrError<typename T::Response>> Session::send(
|
future<ResponseOrError<typename T::Response>> Session::send(const T& request) {
|
||||||
const T& request) {
|
|
||||||
using Response = typename T::Response;
|
using Response = typename T::Response;
|
||||||
auto promise = std::make_shared<std::promise<ResponseOrError<Response>>>();
|
promise<ResponseOrError<Response>> promise;
|
||||||
const TypeInfo* typeinfo = TypeOf<T>::type();
|
const TypeInfo* typeinfo = TypeOf<T>::type();
|
||||||
auto sent =
|
auto sent =
|
||||||
send(typeinfo, &request, [=](const void* result, const Error* error) {
|
send(typeinfo, &request, [=](const void* result, const Error* error) {
|
||||||
if (error != nullptr) {
|
if (error != nullptr) {
|
||||||
promise->set_value(ResponseOrError<Response>(*error));
|
promise.set_value(ResponseOrError<Response>(*error));
|
||||||
} else {
|
} else {
|
||||||
promise->set_value(ResponseOrError<Response>(
|
promise.set_value(ResponseOrError<Response>(
|
||||||
*reinterpret_cast<const Response*>(result)));
|
*reinterpret_cast<const Response*>(result)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!sent) {
|
if (!sent) {
|
||||||
promise->set_value(Error("Failed to send request"));
|
promise.set_value(Error("Failed to send request"));
|
||||||
}
|
}
|
||||||
return promise->get_future();
|
return promise.get_future();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename>
|
template <typename T, typename>
|
||||||
|
@ -22,8 +22,10 @@
|
|||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
namespace dap {
|
namespace dap {
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user