Branch data Line data Source code
1 : : /*
2 : : * Copyright 2025 Jinwoo Sung
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License");
5 : : * you may not use this file except in compliance with the License.
6 : : * You may obtain a copy of the License at
7 : : *
8 : : * http://www.apache.org/licenses/LICENSE-2.0
9 : : *
10 : : * Unless required by applicable law or agreed to in writing, software
11 : : * distributed under the License is distributed on an "AS IS" BASIS,
12 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : : * See the License for the specific language governing permissions and
14 : : * limitations under the License.
15 : : */
16 : :
17 : : #pragma once
18 : :
19 : : #include <memory>
20 : : #include <string>
21 : : #include <string_view>
22 : : #include <utility>
23 : :
24 : : #include "wirestead/diagnostics/logger.hpp"
25 : :
26 : : // #449: a blocking send (Reliable-mode send()/send_blocking()/send_move()/
27 : : // send_shared()) called from inside a data/message callback deadlocks -
28 : : // clearing backpressure requires progress on the same io thread that a
29 : : // blocking wait would now be stuck on. This thread_local flag, set for the
30 : : // duration of any data/message callback dispatch, lets the blocking-send
31 : : // path detect that scenario and fail fast (return false) instead of
32 : : // blocking forever. It intentionally isn't scoped per-channel: if the
33 : : // current thread is inside ANY callback dispatch, that thread can't make
34 : : // progress on I/O regardless of which channel triggered the callback -
35 : : // including a second channel sharing the same io_context/thread.
36 : : namespace wirestead {
37 : : namespace wrapper {
38 : : namespace detail {
39 : :
40 : : // A depth counter rather than a bool so that a nested/reentrant
41 : : // CallbackGuard on the same thread doesn't clear the flag out from under an
42 : : // outer guard that's still in scope: the inner guard's destructor would
43 : : // otherwise flip g_callback_depth back to "not in a callback" while the
44 : : // outer dispatch is still running, reopening the #449 deadlock this guard
45 : : // exists to prevent.
46 : : inline thread_local int g_callback_depth = 0;
47 : :
48 : : class CallbackGuard {
49 : : public:
50 : 2656 : CallbackGuard() { ++g_callback_depth; }
51 : 2656 : ~CallbackGuard() { --g_callback_depth; }
52 : : CallbackGuard(const CallbackGuard&) = delete;
53 : : CallbackGuard& operator=(const CallbackGuard&) = delete;
54 : : };
55 : :
56 : 5 : inline bool in_data_callback() { return g_callback_depth > 0; }
57 : :
58 : : // Invokes a user-supplied wrapper callback (on_data/on_message/on_connect/
59 : : // on_disconnect/on_error/on_backpressure and their batch variants) and
60 : : // prevents an exception escaping it from propagating further. All channels
61 : : // share one process-wide IoContextManager thread by default
62 : : // (concurrency/io_context_manager.cc); an uncaught exception here would
63 : : // otherwise escape the un-guarded handler call, propagate out of
64 : : // io_context::run(), and stop I/O for every channel sharing that context -
65 : : // not just the one whose callback misbehaved.
66 : : template <typename Callback, typename... Args>
67 : 4297 : void invoke_user_callback(std::string_view component, std::string_view operation, const Callback& callback,
68 : : Args&&... args) {
69 [ + + ]: 4297 : if (!callback) return;
70 : : try {
71 : 3737 : callback(std::forward<Args>(args)...);
72 : 0 : } catch (const std::exception& e) {
73 : 0 : WIRESTEAD_LOG_ERROR(component, operation, "Uncaught exception in user callback: " + std::string(e.what()));
74 : 0 : } catch (...) {
75 : 0 : WIRESTEAD_LOG_ERROR(component, operation, "Uncaught non-standard exception in user callback");
76 : : }
77 : : }
78 : :
79 : : // Overload for a handler snapshotted as a shared pointer rather than copied
80 : : // out of its guarded member - see interface::SharedCallback. A null pointer
81 : : // means "not registered", exactly as an empty std::function does above.
82 : : template <typename Callback, typename... Args>
83 : 3796 : void invoke_user_callback(std::string_view component, std::string_view operation,
84 : : const std::shared_ptr<const Callback>& callback, Args&&... args) {
85 [ + + ]: 3796 : if (!callback) return;
86 : 3644 : invoke_user_callback(component, operation, *callback, std::forward<Args>(args)...);
87 : : }
88 : :
89 : : } // namespace detail
90 : : } // namespace wrapper
91 : : } // namespace wirestead
|