Repository navigation
Expand file tree
/
Copy pathhook_bus.cpp
More file actions
129 lines (108 loc) · 5.33 KB
/
Copy pathhook_bus.cpp
File metadata and controls
129 lines (108 loc) · 5.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*
This file is part of libhttpserver
Copyright (C) 2011-2026 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the file
LICENSE in the distribution; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
// TASK-118 step 2: the hook bus storage and the hook_handle lifetime
// (plan D4). Per-phase vectors behind one shared mutex, a per-phase
// atomic emptiness gate (the zero-cost-when-unused contract), and
// snapshot-copy firing: fire() copies the phase's shared_ptr slots
// under the shared lock and invokes them outside it, so a hook may add
// or remove registrations mid-fire without disturbing the running pass
// and without deadlocking (add/remove take the unique lock; fire
// holds only the shared lock while copying).
#include <array>
#include <atomic>
#include <cstdint>
#include <memory>
#include <shared_mutex>
#include <utility>
#include <vector>
#include <httpserver/detail/hook_bus.hpp>
#include <httpserver/server/hooks.hpp>
namespace httpserver::server::detail {
class hook_bus_impl {
public:
using erased = concurrency::unique_function<hook_action(void*)>;
// One registration: the identity key and the type-erased invoke.
// Shared ownership is what makes snapshot-copy firing possible
// (the erased callable is move-only, the snapshot copies slots).
struct registration {
std::uint64_t key;
std::shared_ptr<erased> call;
};
std::uint64_t add(std::uint8_t phase, erased call) {
std::unique_lock<std::shared_mutex> lock(mu_);
const std::uint64_t key = next_key_++;
slots_[phase].push_back(registration{key, std::make_shared<erased>(std::move(call))});
any_[phase].store(true, std::memory_order_release);
return key;
}
void remove(std::uint8_t phase, std::uint64_t key) noexcept {
std::unique_lock<std::shared_mutex> lock(mu_);
std::vector<registration>& slots = slots_[phase];
for (std::vector<registration>::iterator it = slots.begin(); it != slots.end(); ++it) {
if (it->key != key) continue;
slots.erase(it);
break;
}
if (slots.empty()) {
any_[phase].store(false, std::memory_order_release);
}
}
bool any(std::uint8_t phase) const noexcept { return any_[phase].load(std::memory_order_acquire); }
hook_action fire(std::uint8_t phase, void* ctx, std::exception_ptr* contained) {
std::vector<std::shared_ptr<erased>> snapshot;
{
std::shared_lock<std::shared_mutex> lock(mu_);
const std::vector<registration>& slots = slots_[phase];
if (slots.empty()) return hook_action::pass();
snapshot.reserve(slots.size());
for (const registration& slot : slots) {
snapshot.push_back(slot.call);
}
}
for (const std::shared_ptr<erased>& call : snapshot) {
hook_action outcome;
try {
outcome = (*call)(ctx);
} catch (...) {
// A throwing hook is treated as pass() and the chain
// continues (the v2 rule). The first contained throw
// is handed to the caller for the D4 surfacing at the
// dispatcher; the logging surface for the swallowed
// diagnostic is a deferred milestone item.
if (contained != nullptr && *contained == nullptr) {
*contained = std::current_exception();
}
continue;
}
if (!outcome.is_pass()) return outcome;
}
return hook_action::pass();
}
private:
static constexpr std::size_t k_phases = static_cast<std::size_t>(hook_phase::count_);
std::array<std::vector<registration>, k_phases> slots_;
std::array<std::atomic<bool>, k_phases> any_{};
std::shared_mutex mu_;
std::uint64_t next_key_ = 1; // 0 is the disarmed-handle sentinel
};
std::shared_ptr<hook_bus_impl> hook_bus_create() { return std::make_shared<hook_bus_impl>(); }
std::uint64_t hook_bus_add(const std::shared_ptr<hook_bus_impl>& owner, std::uint8_t phase, concurrency::unique_function<hook_action(void*)> call) { return owner->add(phase, std::move(call)); }
void hook_bus_remove(const std::shared_ptr<hook_bus_impl>& owner, std::uint8_t phase, std::uint64_t key) noexcept { owner->remove(phase, key); }
bool hook_bus_any(const std::shared_ptr<hook_bus_impl>& owner, std::uint8_t phase) noexcept { return owner->any(phase); }
hook_action hook_bus_fire(const std::shared_ptr<hook_bus_impl>& owner, std::uint8_t phase, void* ctx, std::exception_ptr* contained) { return owner->fire(phase, ctx, contained); }
} // namespace httpserver::server::detail