ThreadSchedule 2.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
error_handler.hpp
Go to the documentation of this file.
1#pragma once
2
7
8#include <chrono>
9#include <exception>
10#include <functional>
11#include <future>
12#include <map>
13#include <memory>
14#include <mutex>
15#include <string>
16#include <thread>
17#include <type_traits>
18#include <vector>
19
20namespace threadschedule
21{
22
33{
35 std::exception_ptr exception;
36
38 std::string task_description;
39
41 std::thread::id thread_id;
42
44 std::chrono::steady_clock::time_point timestamp;
45
52 static auto capture(std::string description = {}) -> TaskError
53 {
54 TaskError err;
55 err.exception = std::current_exception();
56 err.task_description = std::move(description);
57 err.thread_id = std::this_thread::get_id();
58 err.timestamp = std::chrono::steady_clock::now();
59 return err;
60 }
61
73 [[nodiscard]] auto what() const -> std::string
74 {
75 try
76 {
77 if (exception)
78 {
79 std::rethrow_exception(exception);
80 }
81 }
82 catch (std::exception const& e)
83 {
84 return e.what();
85 }
86 catch (...)
87 {
88 return "Unknown exception";
89 }
90 return "No exception";
91 }
92
102 void rethrow() const
103 {
104 if (exception)
105 {
106 std::rethrow_exception(exception);
107 }
108 }
109};
110
116using ErrorCallback = std::function<void(TaskError const&)>;
117
143{
144 public:
151 auto add_callback(ErrorCallback callback) -> size_t
152 {
153 std::lock_guard<std::mutex> lock(mutex_);
154 size_t const id = next_callback_id_++;
155 callbacks_.emplace(id, std::move(callback));
156 return id;
157 }
158
165 auto remove_callback(size_t id) -> bool
166 {
167 std::lock_guard<std::mutex> lock(mutex_);
168 return callbacks_.erase(id) > 0;
169 }
170
174 [[nodiscard]] auto has_callback(size_t id) const -> bool
175 {
176 std::lock_guard<std::mutex> lock(mutex_);
177 return callbacks_.count(id) > 0;
178 }
179
187 {
188 std::lock_guard<std::mutex> lock(mutex_);
189 callbacks_.clear();
190 }
191
201 void handle_error(TaskError const& error)
202 {
203 std::lock_guard<std::mutex> lock(mutex_);
204 error_count_++;
205
206 for (auto const& [id, callback] : callbacks_)
207 {
208 try
209 {
210 callback(error);
211 }
212 catch (...)
213 {
214 }
215 }
216 }
217
226 [[nodiscard]] auto error_count() const -> size_t
227 {
228 std::lock_guard<std::mutex> lock(mutex_);
229 return error_count_;
230 }
231
236 {
237 std::lock_guard<std::mutex> lock(mutex_);
238 error_count_ = 0;
239 }
240
241 private:
242 mutable std::mutex mutex_;
243 std::map<size_t, ErrorCallback> callbacks_;
244 size_t next_callback_id_{0};
245 size_t error_count_{0};
246};
247
266template <typename Func>
268{
269 public:
270 ErrorHandledTask(Func&& func, std::shared_ptr<ErrorHandler> handler, std::string description = "")
271 : func_(std::forward<Func>(func)), handler_(std::move(handler)), description_(std::move(description))
272 {
273 }
274
276 {
277 try
278 {
279 func_();
280 }
281 catch (...)
282 {
283 if (handler_)
284 handler_->handle_error(TaskError::capture(description_));
285 }
286 }
287
288 private:
289 Func func_;
290 std::shared_ptr<ErrorHandler> handler_;
291 std::string description_;
292};
293
303template <typename Func>
304auto make_error_handled_task(Func&& func, std::shared_ptr<ErrorHandler> handler, std::string description = "")
305{
306 return ErrorHandledTask<Func>(std::forward<Func>(func), std::move(handler), std::move(description));
307}
308
328template <typename T>
330{
331 public:
332 explicit FutureWithErrorHandler(std::future<T> future)
333 : future_(std::move(future)), error_callback_(nullptr), has_callback_(false)
334 {
335 }
336
341
352 auto on_error(std::function<void(std::exception_ptr)> callback) -> FutureWithErrorHandler&
353 {
354 error_callback_ = std::move(callback);
355 has_callback_ = true;
356 return *this;
357 }
358
368 auto get() -> T
369 {
370 try
371 {
372 if constexpr (std::is_void_v<T>)
373 future_.get();
374 else
375 return future_.get();
376 }
377 catch (...)
378 {
379 if (has_callback_ && error_callback_)
380 {
381 error_callback_(std::current_exception());
382 }
383 throw;
384 }
385 }
386
392 void wait() const
393 {
394 future_.wait();
395 }
396
404 template <typename Rep, typename Period>
405 auto wait_for(std::chrono::duration<Rep, Period> const& timeout_duration) const
406 {
407 return future_.wait_for(timeout_duration);
408 }
409
417 template <typename Clock, typename Duration>
418 auto wait_until(std::chrono::time_point<Clock, Duration> const& timeout_time) const
419 {
420 return future_.wait_until(timeout_time);
421 }
422
428 [[nodiscard]] auto valid() const -> bool
429 {
430 return future_.valid();
431 }
432
433 private:
434 std::future<T> future_;
435 std::function<void(std::exception_ptr)> error_callback_;
436 bool has_callback_{false};
437};
438
439} // namespace threadschedule
Callable wrapper that catches exceptions and routes them to an ErrorHandler.
ErrorHandledTask(Func &&func, std::shared_ptr< ErrorHandler > handler, std::string description="")
Central registry and dispatcher for task-error callbacks.
auto remove_callback(size_t id) -> bool
Remove a single callback by its ID.
auto has_callback(size_t id) const -> bool
Check whether a callback with the given ID is registered.
void reset_error_count()
Reset the cumulative error count to zero.
void handle_error(TaskError const &error)
Dispatch an error to all registered callbacks.
void clear_callbacks()
Remove all registered error callbacks.
auto error_count() const -> size_t
Return the total number of errors handled since the last reset.
auto add_callback(ErrorCallback callback) -> size_t
Register an error callback.
auto on_error(std::function< void(std::exception_ptr)> callback) -> FutureWithErrorHandler &
Attach an error callback.
auto wait_until(std::chrono::time_point< Clock, Duration > const &timeout_time) const
Block until the result is ready or the given time point is reached.
auto operator=(FutureWithErrorHandler const &) -> FutureWithErrorHandler &=delete
auto get() -> T
Retrieve the result, invoking the error callback on failure.
auto valid() const -> bool
Check whether the future refers to a shared state.
auto operator=(FutureWithErrorHandler &&) -> FutureWithErrorHandler &=default
FutureWithErrorHandler(std::future< T > future)
FutureWithErrorHandler(FutureWithErrorHandler &&)=default
auto wait_for(std::chrono::duration< Rep, Period > const &timeout_duration) const
Block until the result is ready or the timeout elapses.
FutureWithErrorHandler(FutureWithErrorHandler const &)=delete
void wait() const
Block until the result is ready.
auto make_error_handled_task(Func &&func, std::shared_ptr< ErrorHandler > handler, std::string description="")
Factory function that creates an ErrorHandledTask with perfect forwarding.
std::function< void(TaskError const &)> ErrorCallback
Signature for error-handling callbacks registered with ErrorHandler.
Holds diagnostic information captured from a failed task.
std::string task_description
Optional human-readable label supplied when the task was submitted.
void rethrow() const
Re-throw the original exception.
static auto capture(std::string description={}) -> TaskError
Capture the current in-flight exception into a TaskError.
std::exception_ptr exception
The captured exception. Never null when produced by the library.
auto what() const -> std::string
Extract the message string from the stored exception.
std::thread::id thread_id
Id of the thread on which the exception was thrown.
std::chrono::steady_clock::time_point timestamp
Monotonic timestamp recorded immediately after the exception was caught.