ThreadSchedule 2.2.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 "callable.hpp"
9#include <chrono>
10#include <exception>
11#include <functional>
12#include <future>
13#include <map>
14#include <memory>
15#include <mutex>
16#include <string>
17#include <thread>
18#include <type_traits>
19#include <vector>
20
21namespace threadschedule
22{
23
34{
36 std::exception_ptr exception;
37
39 std::string task_description;
40
42 std::thread::id thread_id;
43
45 std::chrono::steady_clock::time_point timestamp;
46
53 static auto capture(std::string description = {}) -> TaskError
54 {
55 TaskError err;
56 err.exception = std::current_exception();
57 err.task_description = std::move(description);
58 err.thread_id = std::this_thread::get_id();
59 err.timestamp = std::chrono::steady_clock::now();
60 return err;
61 }
62
74 [[nodiscard]] auto what() const -> std::string
75 {
76 try
77 {
78 if (exception)
79 {
80 std::rethrow_exception(exception);
81 }
82 }
83 catch (std::exception const& e)
84 {
85 return e.what();
86 }
87 catch (...)
88 {
89 return "Unknown exception";
90 }
91 return "No exception";
92 }
93
103 void rethrow() const
104 {
105 if (exception)
106 {
107 std::rethrow_exception(exception);
108 }
109 }
110};
111
118
120using FutureErrorCallback = detail::move_callable<void(std::exception_ptr)>;
121
147{
148 public:
155 auto add_callback(ErrorCallback callback) -> size_t
156 {
157 return emplace_callback(ErrorCallbackStorage(std::move(callback)));
158 }
159
160 template <typename Callback,
161 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>, ErrorCallback>, int> = 0>
162 auto add_callback(Callback&& callback) -> size_t
163 {
164 static_assert(std::is_invocable_r_v<void, Callback&, TaskError const&>,
165 "Error callback must be invocable with TaskError const&");
166 return emplace_callback(detail::make_copyable_callable<void(TaskError const&)>(std::forward<Callback>(callback)));
167 }
168
175 auto remove_callback(size_t id) -> bool
176 {
177 std::lock_guard<std::mutex> lock(mutex_);
178 return callbacks_.erase(id) > 0;
179 }
180
184 [[nodiscard]] auto has_callback(size_t id) const -> bool
185 {
186 std::lock_guard<std::mutex> lock(mutex_);
187 return callbacks_.count(id) > 0;
188 }
189
197 {
198 std::lock_guard<std::mutex> lock(mutex_);
199 callbacks_.clear();
200 }
201
211 void handle_error(TaskError const& error)
212 {
213 std::vector<ErrorCallbackStorage> snapshot;
214 {
215 std::lock_guard<std::mutex> lock(mutex_);
216 error_count_++;
217 snapshot.reserve(callbacks_.size());
218 for (auto const& [id, callback] : callbacks_)
219 snapshot.push_back(callback);
220 }
221
222 for (auto& callback : snapshot)
223 {
224 try
225 {
226 callback(error);
227 }
228 catch (...)
229 {
230 }
231 }
232 }
233
242 [[nodiscard]] auto error_count() const -> size_t
243 {
244 std::lock_guard<std::mutex> lock(mutex_);
245 return error_count_;
246 }
247
252 {
253 std::lock_guard<std::mutex> lock(mutex_);
254 error_count_ = 0;
255 }
256
257 private:
258 auto emplace_callback(ErrorCallbackStorage callback) -> size_t
259 {
260 std::lock_guard<std::mutex> lock(mutex_);
261 size_t const id = next_callback_id_++;
262 callbacks_.emplace(id, std::move(callback));
263 return id;
264 }
265
266 mutable std::mutex mutex_;
267 std::map<size_t, ErrorCallbackStorage> callbacks_;
268 size_t next_callback_id_{0};
269 size_t error_count_{0};
270};
271
290template <typename Func>
292{
293 public:
294 ErrorHandledTask(Func&& func, std::shared_ptr<ErrorHandler> handler, std::string description = "")
295 : func_(std::forward<Func>(func)), handler_(std::move(handler)), description_(std::move(description))
296 {
297 }
298
300 {
301 try
302 {
303 func_();
304 }
305 catch (...)
306 {
307 if (handler_)
308 handler_->handle_error(TaskError::capture(description_));
309 }
310 }
311
312 private:
313 Func func_;
314 std::shared_ptr<ErrorHandler> handler_;
315 std::string description_;
316};
317
327template <typename Func>
328auto make_error_handled_task(Func&& func, std::shared_ptr<ErrorHandler> handler, std::string description = "")
329{
330 return ErrorHandledTask<Func>(std::forward<Func>(func), std::move(handler), std::move(description));
331}
332
352template <typename T>
354{
355 public:
356 explicit FutureWithErrorHandler(std::future<T> future)
357 : future_(std::move(future)), error_callback_(nullptr), has_callback_(false)
358 {
359 }
360
365
376 auto on_error(std::function<void(std::exception_ptr)> callback) -> FutureWithErrorHandler&
377 {
378 error_callback_ = FutureErrorCallback(std::move(callback));
379 has_callback_ = true;
380 return *this;
381 }
382
383 template <typename Callback,
384 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>, std::function<void(std::exception_ptr)>>,
385 int> = 0>
386 auto on_error(Callback&& callback) -> FutureWithErrorHandler&
387 {
388 static_assert(std::is_invocable_r_v<void, Callback&, std::exception_ptr>,
389 "Error callback must be invocable with std::exception_ptr");
390 error_callback_ = detail::make_move_callable<void(std::exception_ptr)>(std::forward<Callback>(callback));
391 has_callback_ = true;
392 return *this;
393 }
394
404 auto get() -> T
405 {
406 try
407 {
408 if constexpr (std::is_void_v<T>)
409 future_.get();
410 else
411 return future_.get();
412 }
413 catch (...)
414 {
415 if (has_callback_ && error_callback_)
416 {
417 error_callback_(std::current_exception());
418 }
419 throw;
420 }
421 }
422
428 void wait() const
429 {
430 future_.wait();
431 }
432
440 template <typename Rep, typename Period>
441 auto wait_for(std::chrono::duration<Rep, Period> const& timeout_duration) const
442 {
443 return future_.wait_for(timeout_duration);
444 }
445
453 template <typename Clock, typename Duration>
454 auto wait_until(std::chrono::time_point<Clock, Duration> const& timeout_time) const
455 {
456 return future_.wait_until(timeout_time);
457 }
458
464 [[nodiscard]] auto valid() const -> bool
465 {
466 return future_.valid();
467 }
468
469 private:
470 std::future<T> future_;
471 FutureErrorCallback error_callback_;
472 bool has_callback_{false};
473};
474
475} // namespace threadschedule
Feature-gated callable storage aliases for modern C++ builds.
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.
auto add_callback(Callback &&callback) -> size_t
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 on_error(Callback &&callback) -> FutureWithErrorHandler &
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.
std::function< Signature > copyable_callable
Definition callable.hpp:40
auto make_move_callable(Callable &&callable) -> move_callable< Signature >
Definition callable.hpp:111
std::function< Signature > move_callable
Definition callable.hpp:32
auto make_copyable_callable(Callable &&callable) -> copyable_callable< Signature >
Definition callable.hpp:117
detail::copyable_callable< void(TaskError const &)> ErrorCallback
Signature for error-handling callbacks registered with ErrorHandler.
auto make_error_handled_task(Func &&func, std::shared_ptr< ErrorHandler > handler, std::string description="")
Factory function that creates an ErrorHandledTask with perfect forwarding.
detail::move_callable< void(std::exception_ptr)> FutureErrorCallback
ErrorCallback ErrorCallbackStorage
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.