./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
41 / 43
95.3%
Branches:
19 / 22
86.4%
Functions:
8 / 8
100.0%

src/event_dispatcher.cpp
Line Branch Exec Source
1 /**
2 * @file event_dispatcher.cpp
3 * @brief The dispatcher's per-thread emit chain and rundown drain.
4 *
5 * EventDispatcher is otherwise a header-only template. What lives here is the part that cannot: the emit chain is
6 * backed by a Win32 TLS index, and the installed detail/ header must stay Win32-free.
7 */
8
9 #include "DetourModKit/detail/event_dispatcher.hpp"
10
11 #include "internal/drain_backoff.hpp"
12 #include "internal/shared_tls_index.hpp"
13 #include "platform.hpp"
14
15 #include <atomic>
16 #include <cstdint>
17 #include <thread>
18
19 namespace DetourModKit::detail
20 {
21 namespace
22 {
23 static_assert(sizeof(DWORD) == sizeof(std::uint32_t));
24
25 /// The TLS index that holds this thread's EmitFrame chain head. Each dispatcher with a publish owns it.
26 constinit SharedTlsIndex s_emit_tls;
27
28 std::atomic<std::uint32_t> s_untracked_emit_frames{0};
29 } // namespace
30
31 10784 void acquire_emit_frame_owner() noexcept
32 {
33 10784 (void)s_emit_tls.acquire();
34 10784 }
35
36 10605 void release_emit_frame_owner() noexcept
37 {
38 10605 s_emit_tls.release();
39 10605 }
40
41 3239883 bool push_emit_frame(EmitFrame &frame) noexcept
42 {
43 3239883 const DWORD index = s_emit_tls.index();
44
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3212912 times.
3212912 if (index == TLS_OUT_OF_INDEXES)
45 {
46 ✗ return false;
47 }
48 3212912 frame.tls_index = index;
49 3212912 frame.prev = static_cast<EmitFrame *>(::TlsGetValue(index));
50 // A store can still fail: an index past the TEB's inline slots is backed by a lazily heap-allocated
51 // expansion array. Report it rather than leave the chain claiming this thread is elsewhere.
52 3190831 return ::TlsSetValue(index, &frame) != FALSE;
53 }
54
55 2983818 void pop_emit_frame(const EmitFrame &frame) noexcept
56 {
57 // The matching push succeeded, so this store cannot fail for want of slot storage. An enclosing frame retains
58 // an owner. The final pop uses the null-restore exception in SharedTlsIndex.
59 2983818 (void)::TlsSetValue(frame.tls_index, frame.prev);
60 3001278 }
61
62 10179 bool thread_is_emitting_dispatcher(const void *dispatcher) noexcept
63 {
64 10179 const DWORD index = s_emit_tls.index();
65
2/2
✓ Branch 3 → 4 taken 9594 times.
✓ Branch 3 → 5 taken 585 times.
10179 if (index == TLS_OUT_OF_INDEXES)
66 {
67 9594 return false;
68 }
69
2/2
✓ Branch 10 → 7 taken 55 times.
✓ Branch 10 → 11 taken 532 times.
587 for (const auto *node = static_cast<const EmitFrame *>(::TlsGetValue(index)); node != nullptr;
70 2 node = node->prev)
71 {
72
2/2
✓ Branch 7 → 8 taken 53 times.
✓ Branch 7 → 9 taken 2 times.
55 if (node->dispatcher == dispatcher)
73 {
74 53 return true;
75 }
76 }
77 532 return false;
78 }
79
80 10788 bool thread_is_emitting_type(const void *type_tag) noexcept
81 {
82 10788 const DWORD index = s_emit_tls.index();
83
2/2
✓ Branch 3 → 4 taken 267 times.
✓ Branch 3 → 5 taken 10521 times.
10788 if (index == TLS_OUT_OF_INDEXES)
84 {
85 267 return false;
86 }
87
2/2
✓ Branch 10 → 7 taken 3 times.
✓ Branch 10 → 11 taken 10518 times.
10521 for (const auto *node = static_cast<const EmitFrame *>(::TlsGetValue(index)); node != nullptr;
88 ✗ node = node->prev)
89 {
90
1/2
✓ Branch 7 → 8 taken 3 times.
✗ Branch 7 → 9 not taken.
3 if (node->type_tag == type_tag)
91 {
92 3 return true;
93 }
94 }
95 10518 return false;
96 }
97
98 20744 std::atomic<std::uint32_t> &untracked_emit_frames() noexcept
99 {
100 20744 return s_untracked_emit_frames;
101 }
102
103 15 Rundown drain_gate(EntryGate &gate, const void *dispatcher) noexcept
104 {
105
5/6
✓ Branch 3 → 4 taken 14 times.
✓ Branch 3 → 12 taken 1 time.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 14 times.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 16 taken 14 times.
29 if (thread_is_emitting_dispatcher(dispatcher) || s_untracked_emit_frames.load(std::memory_order_seq_cst) != 0)
106 {
107 1 return Rundown::Unwaitable;
108 }
109
110 // The tombstone is already published (seq_cst) by the caller, and every invocation increments in_flight
111 // seq_cst before rechecking it. Those two orders are the Dekker seam: an entrant that misses the tombstone
112 // is guaranteed visible here, so reaching zero means no invocation remains and none can start.
113 //
114 // This wait has no timeout. The tombstone closes the entrant set, so it cannot grow. A tracked handler can
115 // still remain parked indefinitely. Self-entry deadlocks, and an untracked entry makes completion
116 // unprovable, so both cases are refused above. Backoff limits CPU use while a tracked handler finishes.
117 14 DrainBackoff backoff;
118
2/2
✓ Branch 25 → 17 taken 132 times.
✓ Branch 25 → 26 taken 14 times.
292 while (gate.in_flight.load(std::memory_order_seq_cst) != 0)
119 {
120 132 backoff.pause();
121 }
122 14 return Rundown::Drained;
123 }
124 } // namespace DetourModKit::detail
125