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
10namespace cpr {
11enum 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 */
19template <typename T, bool isCancellable = false>
20class 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
131template <typename T>
132AsyncWrapper(std::future<T>&&) -> AsyncWrapper<T, false>;
133
134template <typename T>
135AsyncWrapper(std::future<T>&&, std::shared_ptr<std::atomic_bool>&&) -> AsyncWrapper<T, true>;
136
137} // namespace cpr
138
139
140#endif
141