ThreadSchedule 1.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
error_handler.hpp
1#pragma once
2
3#include <chrono>
4#include <exception>
5#include <functional>
6#include <future>
7#include <memory>
8#include <mutex>
9#include <string>
10#include <thread>
11#include <vector>
12
13namespace threadschedule
14{
15
26{
28 std::exception_ptr exception;
29
31 std::string task_description;
32
34 std::thread::id thread_id;
35
37 std::chrono::steady_clock::time_point timestamp;
38
50 [[nodiscard]] auto what() const -> std::string
51 {
52 try
53 {
54 if (exception)
55 {
56 std::rethrow_exception(exception);
57 }
58 }
59 catch (std::exception const& e)
60 {
61 return e.what();
62 }
63 catch (...)
64 {
65 return "Unknown exception";
66 }
67 return "No exception";
68 }
69
79 void rethrow() const
80 {
81 if (exception)
82 {
83 std::rethrow_exception(exception);
84 }
85 }
86};
87
93using ErrorCallback = std::function<void(TaskError const&)>;
94
120{
121 public:
130 auto add_callback(ErrorCallback callback) -> size_t
131 {
132 std::lock_guard<std::mutex> lock(mutex_);
133 callbacks_.push_back(std::move(callback));
134 return callbacks_.size() - 1;
135 }
136
144 {
145 std::lock_guard<std::mutex> lock(mutex_);
146 callbacks_.clear();
147 }
148
158 void handle_error(TaskError const& error)
159 {
160 std::lock_guard<std::mutex> lock(mutex_);
161 error_count_++;
162
163 for (auto const& callback : callbacks_)
164 {
165 try
166 {
167 callback(error);
168 }
169 catch (...)
170 {
171 // Error handlers should not throw, but we catch just in case
172 }
173 }
174 }
175
184 [[nodiscard]] auto error_count() const -> size_t
185 {
186 std::lock_guard<std::mutex> lock(mutex_);
187 return error_count_;
188 }
189
194 {
195 std::lock_guard<std::mutex> lock(mutex_);
196 error_count_ = 0;
197 }
198
199 private:
200 mutable std::mutex mutex_;
201 std::vector<ErrorCallback> callbacks_;
202 size_t error_count_{0};
203};
204
223template <typename Func>
224class ErrorHandledTask
225{
226 public:
227 ErrorHandledTask(Func&& func, std::shared_ptr<ErrorHandler> handler, std::string description = "")
228 : func_(std::forward<Func>(func)), handler_(std::move(handler)), description_(std::move(description))
229 {
230 }
231
232 void operator()()
233 {
234 try
235 {
236 func_();
237 }
238 catch (...)
239 {
240 if (handler_)
241 {
242 TaskError error;
243 error.exception = std::current_exception();
244 error.task_description = description_;
245 error.thread_id = std::this_thread::get_id();
246 error.timestamp = std::chrono::steady_clock::now();
247
248 handler_->handle_error(error);
249 }
250 }
251 }
252
253 private:
254 Func func_;
255 std::shared_ptr<ErrorHandler> handler_;
256 std::string description_;
257};
258
268template <typename Func>
269auto make_error_handled_task(Func&& func, std::shared_ptr<ErrorHandler> handler, std::string description = "")
270{
271 return ErrorHandledTask<Func>(std::forward<Func>(func), std::move(handler), std::move(description));
272}
273
293template <typename T>
294class FutureWithErrorHandler
295{
296 public:
297 explicit FutureWithErrorHandler(std::future<T> future)
298 : future_(std::move(future)), error_callback_(nullptr), has_callback_(false)
299 {
300 }
301
302 FutureWithErrorHandler(FutureWithErrorHandler const&) = delete;
303 auto operator=(FutureWithErrorHandler const&) -> FutureWithErrorHandler& = delete;
304 FutureWithErrorHandler(FutureWithErrorHandler&&) = default;
305 auto operator=(FutureWithErrorHandler&&) -> FutureWithErrorHandler& = default;
306
317 auto on_error(std::function<void(std::exception_ptr)> callback) -> FutureWithErrorHandler&
318 {
319 error_callback_ = std::move(callback);
320 has_callback_ = true;
321 return *this;
322 }
323
333 auto get() -> T
334 {
335 try
336 {
337 return future_.get();
338 }
339 catch (...)
340 {
341 if (has_callback_ && error_callback_)
342 {
343 error_callback_(std::current_exception());
344 }
345 throw;
346 }
347 }
348
354 void wait() const
355 {
356 future_.wait();
357 }
358
366 template <typename Rep, typename Period>
367 auto wait_for(std::chrono::duration<Rep, Period> const& timeout_duration) const
368 {
369 return future_.wait_for(timeout_duration);
370 }
371
379 template <typename Clock, typename Duration>
380 auto wait_until(std::chrono::time_point<Clock, Duration> const& timeout_time) const
381 {
382 return future_.wait_until(timeout_time);
383 }
384
390 [[nodiscard]] auto valid() const -> bool
391 {
392 return future_.valid();
393 }
394
395 private:
396 std::future<T> future_;
397 std::function<void(std::exception_ptr)> error_callback_;
398 bool has_callback_{false};
399};
400
409template <>
410class FutureWithErrorHandler<void>
411{
412 public:
413 explicit FutureWithErrorHandler(std::future<void> future) : future_(std::move(future)), error_callback_(nullptr)
414 {
415 }
416
417 FutureWithErrorHandler(FutureWithErrorHandler const&) = delete;
418 auto operator=(FutureWithErrorHandler const&) -> FutureWithErrorHandler& = delete;
419 FutureWithErrorHandler(FutureWithErrorHandler&&) = default;
420 auto operator=(FutureWithErrorHandler&&) -> FutureWithErrorHandler& = default;
421
422 auto on_error(std::function<void(std::exception_ptr)> callback) -> FutureWithErrorHandler&
423 {
424 error_callback_ = std::move(callback);
425 has_callback_ = true;
426 return *this;
427 }
428
429 void get()
430 {
431 try
432 {
433 future_.get();
434 }
435 catch (...)
436 {
437 if (has_callback_ && error_callback_)
438 {
439 error_callback_(std::current_exception());
440 }
441 throw;
442 }
443 }
444
445 void wait() const
446 {
447 future_.wait();
448 }
449
450 template <typename Rep, typename Period>
451 auto wait_for(std::chrono::duration<Rep, Period> const& timeout_duration) const
452 {
453 return future_.wait_for(timeout_duration);
454 }
455
456 template <typename Clock, typename Duration>
457 auto wait_until(std::chrono::time_point<Clock, Duration> const& timeout_time) const
458 {
459 return future_.wait_until(timeout_time);
460 }
461
462 [[nodiscard]] auto valid() const -> bool
463 {
464 return future_.valid();
465 }
466
467 private:
468 std::future<void> future_;
469 std::function<void(std::exception_ptr)> error_callback_;
470 bool has_callback_{};
471};
472
473} // namespace threadschedule
Callable wrapper that catches exceptions and routes them to an ErrorHandler.
Central registry and dispatcher for task-error callbacks.
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 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 wait_for(std::chrono::duration< Rep, Period > const &timeout_duration) const
Block until the result is ready or the timeout elapses.
void wait() const
Block until the result is ready.
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.
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.