1 | #ifndef CPR_ASYNC_WRAPPER_H |
2 | #define CPR_ASYNC_WRAPPER_H |
3 | |
4 | #include <atomic> |
5 | #include <future> |
6 | #include <memory> |
7 | |
8 | #include "cpr/response.h" |
9 | |
10 | namespace cpr { |
11 | enum class [[nodiscard]] CancellationResult { failure, success, invalid_operation }; |
12 | |
13 | /** |
14 | * A class template intended to wrap results of async operations (instances of std::future<T>) |
15 | * and also provide extended capablilities relaed to these requests, for example cancellation. |
16 | * |
17 | * The RAII semantics are the same as std::future<T> - moveable, not copyable. |
18 | */ |
19 | template <typename T, bool isCancellable = false> |
20 | class AsyncWrapper { |
21 | private: |
22 | std::future<T> future; |
23 | std::shared_ptr<std::atomic_bool> is_cancelled; |
24 | |
25 | public: |
26 | // Constructors |
27 | explicit AsyncWrapper(std::future<T>&& f) : future{std::move(f)} {} |
28 | AsyncWrapper(std::future<T>&& f, std::shared_ptr<std::atomic_bool>&& cancelledState) : future{std::move(f)}, is_cancelled{std::move(cancelledState)} {} |
29 | |
30 | // Copy Semantics |
31 | AsyncWrapper(const AsyncWrapper&) = delete; |
32 | AsyncWrapper& operator=(const AsyncWrapper&) = delete; |
33 | |
34 | // Move Semantics |
35 | AsyncWrapper(AsyncWrapper&&) noexcept = default; |
36 | AsyncWrapper& operator=(AsyncWrapper&&) noexcept = default; |
37 | |
38 | // Destructor |
39 | ~AsyncWrapper() { |
40 | if constexpr (isCancellable) { |
41 | if(is_cancelled) { |
42 | is_cancelled->store(i: true); |
43 | } |
44 | } |
45 | } |
46 | // These methods replicate the behaviour of std::future<T> |
47 | [[nodiscard]] T get() { |
48 | if constexpr (isCancellable) { |
49 | if (IsCancelled()) { |
50 | throw std::logic_error{"Calling AsyncWrapper::get on a cancelled request!" }; |
51 | } |
52 | } |
53 | if (!future.valid()) { |
54 | throw std::logic_error{"Calling AsyncWrapper::get when the associated future instance is invalid!" }; |
55 | } |
56 | return future.get(); |
57 | } |
58 | |
59 | [[nodiscard]] bool valid() const noexcept { |
60 | if constexpr (isCancellable) { |
61 | return !is_cancelled->load() && future.valid(); |
62 | } else { |
63 | return future.valid(); |
64 | } |
65 | } |
66 | |
67 | void wait() const { |
68 | if constexpr (isCancellable) { |
69 | if (is_cancelled->load()) { |
70 | throw std::logic_error{"Calling AsyncWrapper::wait when the associated future is invalid or cancelled!" }; |
71 | } |
72 | } |
73 | if (!future.valid()) { |
74 | throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is invalid!" }; |
75 | } |
76 | future.wait(); |
77 | } |
78 | |
79 | template <class Rep, class Period> |
80 | std::future_status wait_for(const std::chrono::duration<Rep, Period>& timeout_duration) const { |
81 | if constexpr (isCancellable) { |
82 | if (IsCancelled()) { |
83 | throw std::logic_error{"Calling AsyncWrapper::wait_for when the associated future is cancelled!" }; |
84 | } |
85 | } |
86 | if (!future.valid()) { |
87 | throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is invalid!" }; |
88 | } |
89 | return future.wait_for(timeout_duration); |
90 | } |
91 | |
92 | template <class Clock, class Duration> |
93 | std::future_status wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time) const { |
94 | if constexpr (isCancellable) { |
95 | if (IsCancelled()) { |
96 | throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is cancelled!" }; |
97 | } |
98 | } |
99 | if (!future.valid()) { |
100 | throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is invalid!" }; |
101 | } |
102 | return future.wait_until(timeout_time); |
103 | } |
104 | |
105 | std::shared_future<T> share() noexcept { |
106 | return future.share(); |
107 | } |
108 | |
109 | // Cancellation-related methods |
110 | CancellationResult Cancel() { |
111 | if constexpr (!isCancellable) { |
112 | return CancellationResult::invalid_operation; |
113 | } |
114 | if (!future.valid() || is_cancelled->load()) { |
115 | return CancellationResult::invalid_operation; |
116 | } |
117 | is_cancelled->store(i: true); |
118 | return CancellationResult::success; |
119 | } |
120 | |
121 | [[nodiscard]] bool IsCancelled() const { |
122 | if constexpr (isCancellable) { |
123 | return is_cancelled->load(); |
124 | } else { |
125 | return false; |
126 | } |
127 | } |
128 | }; |
129 | |
130 | // Deduction guides |
131 | template <typename T> |
132 | AsyncWrapper(std::future<T>&&) -> AsyncWrapper<T, false>; |
133 | |
134 | template <typename T> |
135 | AsyncWrapper(std::future<T>&&, std::shared_ptr<std::atomic_bool>&&) -> AsyncWrapper<T, true>; |
136 | |
137 | } // namespace cpr |
138 | |
139 | |
140 | #endif |
141 | |