]>
Commit | Line | Data |
---|---|---|
12471842 PL |
1 | /* |
2 | * This file is part of PowerDNS or dnsdist. | |
3 | * Copyright -- PowerDNS.COM B.V. and its contributors | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of version 2 of the GNU General Public License as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * In addition, for the avoidance of any doubt, permission is granted to | |
10 | * link this program with OpenSSL and to (re)distribute the binaries | |
11 | * produced as the result of such linking. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
21 | */ | |
e8c59f2d | 22 | #pragma once |
ab3e8a6c | 23 | #include <boost/any.hpp> |
ac3da0c2 RG |
24 | #include <boost/multi_index_container.hpp> |
25 | #include <boost/multi_index/ordered_index.hpp> | |
26 | #include <boost/multi_index/hashed_index.hpp> | |
27 | #include <boost/multi_index/key_extractors.hpp> | |
0bff046b | 28 | #include <vector> |
ab3e8a6c BH |
29 | #include <map> |
30 | #include <stdexcept> | |
31 | #include <string> | |
f1d51ff7 | 32 | #include <sys/time.h> |
ab3e8a6c | 33 | |
ac3da0c2 RG |
34 | using namespace ::boost::multi_index; |
35 | ||
ab3e8a6c BH |
36 | class FDMultiplexerException : public std::runtime_error |
37 | { | |
38 | public: | |
e16e673d RG |
39 | FDMultiplexerException(const std::string& str) : |
40 | std::runtime_error(str) | |
ab3e8a6c BH |
41 | {} |
42 | }; | |
43 | ||
a1dfcec8 BH |
44 | /** Very simple FD multiplexer, based on callbacks and boost::any parameters |
45 | As a special service, this parameter is kept around and can be modified, | |
46 | allowing for state to be stored inside the multiplexer. | |
47 | ||
48 | It has some "interesting" semantics | |
49 | */ | |
1f4abb20 | 50 | |
ab3e8a6c BH |
51 | class FDMultiplexer |
52 | { | |
d8f6d49f | 53 | public: |
d8f6d49f | 54 | typedef boost::any funcparam_t; |
905dae56 | 55 | typedef std::function<void(int, funcparam_t&)> callbackfunc_t; |
e16e673d RG |
56 | enum class EventKind : uint8_t |
57 | { | |
58 | Read, | |
59 | Write, | |
60 | Both | |
61 | }; | |
d8f6d49f | 62 | |
e16e673d | 63 | protected: |
ab3e8a6c BH |
64 | struct Callback |
65 | { | |
66 | callbackfunc_t d_callback; | |
ac3da0c2 | 67 | mutable funcparam_t d_parameter; |
0bff046b | 68 | struct timeval d_ttd; |
ac3da0c2 | 69 | int d_fd; |
ab3e8a6c BH |
70 | }; |
71 | ||
72 | public: | |
e16e673d RG |
73 | FDMultiplexer() : |
74 | d_inrun(false) | |
ab3e8a6c BH |
75 | {} |
76 | virtual ~FDMultiplexer() | |
77 | {} | |
78 | ||
7c58a81f RG |
79 | // The maximum number of events processed in a single run, not the maximum of watched descriptors |
80 | static constexpr unsigned int s_maxevents = 1024; | |
81 | /* The maximum number of events processed in a single run will be capped to the | |
82 | minimum value of maxEventsHint and s_maxevents, to reduce memory usage. */ | |
83 | static FDMultiplexer* getMultiplexerSilent(unsigned int maxEventsHint = s_maxevents); | |
e16e673d | 84 | |
0e663c3b | 85 | /* tv will be updated to 'now' before run returns */ |
c0c6d1e7 RG |
86 | /* timeout is in ms, 0 will return immediately, -1 will block until at |
87 | least one descriptor is ready */ | |
dd9c8246 | 88 | /* returns 0 on timeout, -1 in case of error (but all implementations |
c7a9f1b4 RG |
89 | actually throw in that case) and the number of ready events otherwise. |
90 | Note that We might have two events (read AND write) for the same descriptor */ | |
e16e673d | 91 | virtual int run(struct timeval* tv, int timeout = 500) = 0; |
ab3e8a6c | 92 | |
dd9c8246 | 93 | /* timeout is in ms, 0 will return immediately, -1 will block until at least one FD is ready */ |
5bdbb83d RG |
94 | virtual void getAvailableFDs(std::vector<int>& fds, int timeout) = 0; |
95 | ||
a1dfcec8 | 96 | //! Add an fd to the read watch list - currently an fd can only be on one list at a time! |
e16e673d | 97 | void addReadFD(int fd, callbackfunc_t toDo, const funcparam_t& parameter = funcparam_t(), const struct timeval* ttd = nullptr) |
ab3e8a6c | 98 | { |
e16e673d RG |
99 | bool alreadyWatched = d_writeCallbacks.count(fd) > 0; |
100 | ||
101 | if (alreadyWatched) { | |
510d368d | 102 | this->alterFD(fd, EventKind::Write, EventKind::Both); |
e16e673d RG |
103 | } |
104 | else { | |
105 | this->addFD(fd, EventKind::Read); | |
106 | } | |
107 | ||
108 | /* do the addition _after_ so the entry is not added if there is an error */ | |
c92e6020 | 109 | accountingAddFD(d_readCallbacks, fd, std::move(toDo), parameter, ttd); |
ab3e8a6c BH |
110 | } |
111 | ||
a1dfcec8 | 112 | //! Add an fd to the write watch list - currently an fd can only be on one list at a time! |
e16e673d | 113 | void addWriteFD(int fd, callbackfunc_t toDo, const funcparam_t& parameter = funcparam_t(), const struct timeval* ttd = nullptr) |
ab3e8a6c | 114 | { |
e16e673d RG |
115 | bool alreadyWatched = d_readCallbacks.count(fd) > 0; |
116 | ||
117 | if (alreadyWatched) { | |
510d368d | 118 | this->alterFD(fd, EventKind::Read, EventKind::Both); |
e16e673d RG |
119 | } |
120 | else { | |
121 | this->addFD(fd, EventKind::Write); | |
122 | } | |
123 | ||
124 | /* do the addition _after_ so the entry is not added if there is an error */ | |
c92e6020 | 125 | accountingAddFD(d_writeCallbacks, fd, std::move(toDo), parameter, ttd); |
ab3e8a6c BH |
126 | } |
127 | ||
a1dfcec8 | 128 | //! Remove an fd from the read watch list. You can't call this function on an fd that is closed already! |
6dcd28c3 | 129 | /** WARNING: references to 'parameter' become invalid after this function! */ |
e16e673d | 130 | void removeReadFD(int fd) |
ab3e8a6c | 131 | { |
e16e673d RG |
132 | const auto& iter = d_writeCallbacks.find(fd); |
133 | accountingRemoveFD(d_readCallbacks, fd); | |
134 | ||
135 | if (iter != d_writeCallbacks.end()) { | |
510d368d | 136 | this->alterFD(fd, EventKind::Both, EventKind::Write); |
e16e673d RG |
137 | } |
138 | else { | |
139 | this->removeFD(fd, EventKind::Read); | |
140 | } | |
ab3e8a6c BH |
141 | } |
142 | ||
a1dfcec8 | 143 | //! Remove an fd from the write watch list. You can't call this function on an fd that is closed already! |
6dcd28c3 | 144 | /** WARNING: references to 'parameter' become invalid after this function! */ |
e16e673d | 145 | void removeWriteFD(int fd) |
ab3e8a6c | 146 | { |
e16e673d RG |
147 | const auto& iter = d_readCallbacks.find(fd); |
148 | accountingRemoveFD(d_writeCallbacks, fd); | |
149 | ||
150 | if (iter != d_readCallbacks.end()) { | |
510d368d | 151 | this->alterFD(fd, EventKind::Both, EventKind::Read); |
e16e673d RG |
152 | } |
153 | else { | |
154 | this->removeFD(fd, EventKind::Write); | |
155 | } | |
ab3e8a6c BH |
156 | } |
157 | ||
e16e673d | 158 | void setReadTTD(int fd, struct timeval tv, int timeout) |
0bff046b | 159 | { |
27ae2e3c RG |
160 | const auto& it = d_readCallbacks.find(fd); |
161 | if (it == d_readCallbacks.end()) { | |
0bff046b | 162 | throw FDMultiplexerException("attempt to timestamp fd not in the multiplexer"); |
27ae2e3c RG |
163 | } |
164 | ||
ac3da0c2 | 165 | auto newEntry = *it; |
0bff046b | 166 | tv.tv_sec += timeout; |
ac3da0c2 RG |
167 | newEntry.d_ttd = tv; |
168 | d_readCallbacks.replace(it, newEntry); | |
0bff046b BH |
169 | } |
170 | ||
e16e673d | 171 | void setWriteTTD(int fd, struct timeval tv, int timeout) |
702b1925 RG |
172 | { |
173 | const auto& it = d_writeCallbacks.find(fd); | |
174 | if (it == d_writeCallbacks.end()) { | |
175 | throw FDMultiplexerException("attempt to timestamp fd not in the multiplexer"); | |
176 | } | |
177 | ||
ac3da0c2 | 178 | auto newEntry = *it; |
702b1925 | 179 | tv.tv_sec += timeout; |
ac3da0c2 RG |
180 | newEntry.d_ttd = tv; |
181 | d_writeCallbacks.replace(it, newEntry); | |
a6ae6414 BH |
182 | } |
183 | ||
e16e673d | 184 | void alterFDToRead(int fd, callbackfunc_t toDo, const funcparam_t& parameter = funcparam_t(), const struct timeval* ttd = nullptr) |
92329526 | 185 | { |
e16e673d | 186 | accountingRemoveFD(d_writeCallbacks, fd); |
510d368d | 187 | this->alterFD(fd, EventKind::Write, EventKind::Read); |
c92e6020 | 188 | accountingAddFD(d_readCallbacks, fd, std::move(toDo), parameter, ttd); |
92329526 RG |
189 | } |
190 | ||
e16e673d | 191 | void alterFDToWrite(int fd, callbackfunc_t toDo, const funcparam_t& parameter = funcparam_t(), const struct timeval* ttd = nullptr) |
92329526 | 192 | { |
e16e673d | 193 | accountingRemoveFD(d_readCallbacks, fd); |
510d368d | 194 | this->alterFD(fd, EventKind::Read, EventKind::Write); |
c92e6020 | 195 | accountingAddFD(d_writeCallbacks, fd, std::move(toDo), parameter, ttd); |
92329526 RG |
196 | } |
197 | ||
e16e673d | 198 | std::vector<std::pair<int, funcparam_t>> getTimeouts(const struct timeval& tv, bool writes = false) |
0bff046b | 199 | { |
e16e673d | 200 | std::vector<std::pair<int, funcparam_t>> ret; |
905dae56 | 201 | const auto tied = std::tie(tv.tv_sec, tv.tv_usec); |
ac3da0c2 | 202 | auto& idx = writes ? d_writeCallbacks.get<TTDOrderedTag>() : d_readCallbacks.get<TTDOrderedTag>(); |
27ae2e3c | 203 | |
ac3da0c2 | 204 | for (auto it = idx.begin(); it != idx.end(); ++it) { |
905dae56 | 205 | if (it->d_ttd.tv_sec == 0 || tied <= std::tie(it->d_ttd.tv_sec, it->d_ttd.tv_usec)) { |
ac3da0c2 | 206 | break; |
27ae2e3c | 207 | } |
e32a8d46 | 208 | ret.emplace_back(it->d_fd, it->d_parameter); |
27ae2e3c RG |
209 | } |
210 | ||
0bff046b BH |
211 | return ret; |
212 | } | |
213 | ||
7c58a81f | 214 | typedef FDMultiplexer* getMultiplexer_t(unsigned int); |
1f4abb20 BH |
215 | typedef std::multimap<int, getMultiplexer_t*> FDMultiplexermap_t; |
216 | ||
217 | static FDMultiplexermap_t& getMultiplexerMap() | |
218 | { | |
219 | static FDMultiplexermap_t theMap; | |
220 | return theMap; | |
221 | } | |
e16e673d | 222 | |
5bdbb83d | 223 | virtual std::string getName() const = 0; |
1f4abb20 | 224 | |
696e32f5 RG |
225 | size_t getWatchedFDCount(bool writeFDs) const |
226 | { | |
227 | return writeFDs ? d_writeCallbacks.size() : d_readCallbacks.size(); | |
228 | } | |
229 | ||
e16e673d | 230 | void runForAllWatchedFDs(void (*watcher)(bool isRead, int fd, const funcparam_t&, struct timeval)) |
f75b8a3b RG |
231 | { |
232 | for (const auto& entry : d_readCallbacks) { | |
233 | watcher(true, entry.d_fd, entry.d_parameter, entry.d_ttd); | |
234 | } | |
235 | for (const auto& entry : d_writeCallbacks) { | |
236 | watcher(false, entry.d_fd, entry.d_parameter, entry.d_ttd); | |
237 | } | |
238 | } | |
239 | ||
ab3e8a6c | 240 | protected: |
e16e673d RG |
241 | struct FDBasedTag |
242 | { | |
243 | }; | |
244 | struct TTDOrderedTag | |
245 | { | |
246 | }; | |
ac3da0c2 RG |
247 | struct ttd_compare |
248 | { | |
249 | /* we want a 0 TTD (no timeout) to come _after_ everything else */ | |
e16e673d | 250 | bool operator()(const struct timeval& lhs, const struct timeval& rhs) const |
ac3da0c2 RG |
251 | { |
252 | /* special treatment if at least one of the TTD is 0, | |
253 | normal comparison otherwise */ | |
254 | if (lhs.tv_sec == 0 && rhs.tv_sec == 0) { | |
255 | return false; | |
256 | } | |
257 | if (lhs.tv_sec == 0 && rhs.tv_sec != 0) { | |
258 | return false; | |
259 | } | |
260 | if (lhs.tv_sec != 0 && rhs.tv_sec == 0) { | |
261 | return true; | |
262 | } | |
263 | ||
264 | return std::tie(lhs.tv_sec, lhs.tv_usec) < std::tie(rhs.tv_sec, rhs.tv_usec); | |
265 | } | |
266 | }; | |
267 | ||
268 | typedef multi_index_container< | |
269 | Callback, | |
e16e673d RG |
270 | indexed_by< |
271 | hashed_unique<tag<FDBasedTag>, | |
272 | member<Callback, int, &Callback::d_fd>>, | |
273 | ordered_non_unique<tag<TTDOrderedTag>, | |
274 | member<Callback, struct timeval, &Callback::d_ttd>, | |
275 | ttd_compare>>> | |
276 | callbackmap_t; | |
ac3da0c2 | 277 | |
ab3e8a6c | 278 | callbackmap_t d_readCallbacks, d_writeCallbacks; |
ab3e8a6c | 279 | bool d_inrun; |
c454d11b | 280 | |
e16e673d | 281 | void accountingAddFD(callbackmap_t& cbmap, int fd, callbackfunc_t toDo, const funcparam_t& parameter, const struct timeval* ttd) |
c454d11b BH |
282 | { |
283 | Callback cb; | |
ac3da0c2 | 284 | cb.d_fd = fd; |
c92e6020 | 285 | cb.d_callback = std::move(toDo); |
e16e673d | 286 | cb.d_parameter = parameter; |
c454d11b | 287 | memset(&cb.d_ttd, 0, sizeof(cb.d_ttd)); |
27ae2e3c RG |
288 | if (ttd) { |
289 | cb.d_ttd = *ttd; | |
290 | } | |
291 | ||
92329526 | 292 | auto pair = cbmap.insert(std::move(cb)); |
27ae2e3c | 293 | if (!pair.second) { |
e16e673d | 294 | throw FDMultiplexerException("Tried to add fd " + std::to_string(fd) + " to multiplexer twice"); |
27ae2e3c | 295 | } |
c454d11b BH |
296 | } |
297 | ||
e16e673d | 298 | void accountingRemoveFD(callbackmap_t& cbmap, int fd) |
c454d11b | 299 | { |
e16e673d RG |
300 | if (!cbmap.erase(fd)) { |
301 | throw FDMultiplexerException("Tried to remove unlisted fd " + std::to_string(fd) + " from multiplexer"); | |
27ae2e3c | 302 | } |
c454d11b | 303 | } |
92329526 | 304 | |
e16e673d RG |
305 | virtual void addFD(int fd, EventKind kind) = 0; |
306 | /* most implementations do not care about which event has to be removed, except for kqueue */ | |
307 | virtual void removeFD(int fd, EventKind kind) = 0; | |
510d368d RG |
308 | /* most implementations do not care about which event has to be removed, except for kqueue */ |
309 | virtual void alterFD(int fd, EventKind from, EventKind to) | |
92329526 RG |
310 | { |
311 | /* naive implementation */ | |
510d368d RG |
312 | removeFD(fd, from); |
313 | addFD(fd, to); | |
92329526 | 314 | } |
1f4abb20 | 315 | }; |