MySQL 8.0.39
Source Code Documentation
linux_epoll_io_service.h
Go to the documentation of this file.
1/*
2 Copyright (c) 2019, 2024, Oracle and/or its affiliates.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License, version 2.0,
6 as published by the Free Software Foundation.
7
8 This program is designed to work with certain software (including
9 but not limited to OpenSSL) that is licensed under separate terms,
10 as designated in a particular file or component or in included license
11 documentation. The authors of MySQL hereby grant you an additional
12 permission to link the program and your derivative works with the
13 separately licensed software that they have either included with
14 the program or referenced in the documentation.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24*/
25
26#ifndef MYSQL_HARNESS_NET_TS_IMPL_LINUX_EPOLL_IO_SERVICE_H_
27#define MYSQL_HARNESS_NET_TS_IMPL_LINUX_EPOLL_IO_SERVICE_H_
28
29#include "my_config.h" // HAVE_EPOLL
30
31#define USE_EVENTFD
32
33#ifdef HAVE_EPOLL
34#include <chrono>
35#include <mutex>
36#include <optional>
37#include <system_error>
38#include <unordered_map>
39
40#if defined(USE_EVENTFD)
41#include <sys/eventfd.h>
42#endif
43
44#include <iostream>
45#include <sstream>
46
51
52namespace net {
53// See
54//
55// - https://idea.popcount.org/2017-02-20-epoll-is-fundamentally-broken-12/
56// - https://idea.popcount.org/2017-03-20-epoll-is-fundamentally-broken-22/
58 public:
60
61 static constexpr const int kSettableEvents = EPOLLIN | EPOLLOUT;
62 static constexpr const int kAlwaysEnabledEvents = EPOLLHUP | EPOLLERR;
63 static constexpr const int kAllEvents =
65
67
68 bool is_open() const noexcept {
73 }
74
76 if (is_open()) {
79 }
80
81 auto res = impl::epoll::create();
82 if (!res) return stdx::make_unexpected(res.error());
83
84 epfd_ = *res;
85#if defined(USE_EVENTFD)
86 notify_fd_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
89
90 return {};
91 }
92#endif
93 auto pipe_res = impl::file::pipe(O_NONBLOCK);
94 if (!pipe_res) return stdx::make_unexpected(pipe_res.error());
95
96 wakeup_fds_ = *pipe_res;
97
98 // set both ends of the pipe non-blocking as
99 //
100 // - read() shouldn't block is pipe is empty
101 // - write() shouldn't block is pipe is full as it only matters there is
102 // something in the pipe to wakeup the poll_one()
103 auto non_block_wakeup_0_res =
105 if (!non_block_wakeup_0_res) return non_block_wakeup_0_res;
106 auto non_block_wakeup_1_res =
108 if (!non_block_wakeup_1_res) return non_block_wakeup_1_res;
109
112
113 return {};
114 }
115
116 void on_notify() {
118 uint64_t some{1};
119 ssize_t res;
120 do {
121 res = ::read(notify_fd_, &some, sizeof(some));
122 // in case of EINTR, loop again
123 // otherwise exit
124 //
125 // no need to loop again on success, as the read() will reset the
126 // counter to 0 anyway.
127 } while (res == -1 && errno == EINTR);
128 } else {
129 std::array<uint8_t, 256> buf;
130 ssize_t res;
131 do {
132 res = ::read(wakeup_fds_.first, buf.data(), buf.size());
133 // in case of EINTR, loop again
134 // in case of read > 0, loop again
135 // otherwise exist
136 } while (res > 0 || (res == -1 && errno == EINTR));
137 }
138 }
139
140 /**
141 * notify the poll_one() that something may have changed.
142 *
143 * can be called from another thread then poll_one().
144 */
145 void notify() override {
146 if (!is_open()) return;
147
148 // optimization idea:
149 //
150 // if notify() runs in the same thread as poll_one() runs in,
151 // then there is no need to interrupt the poll_one() as it couldn't be
152 // running
153 //
154 // it would save the poll_one(), read(), write() call.
155
157 ssize_t res;
158 do {
159 uint64_t one{1};
160 res = ::write(notify_fd_, &one, sizeof(one));
161 // retry if interrupted
162 } while ((res == -1) && (errno == EINTR));
163 } else {
164 ssize_t res;
165 do {
166 res = ::write(wakeup_fds_.second, ".", 1);
167 // retry if interrupted
168 } while ((res == -1) && (errno == EINTR));
169 }
170 }
171
174 remove_fd(wakeup_fds_.first);
175
178 }
179
183 }
184
188 }
189
193 }
194
195 return {};
196 }
197
199 public:
202 bool oneshot) {
203 uint32_t new_events{};
204 switch (wt) {
206 new_events = EPOLLIN;
207 break;
209 new_events = EPOLLOUT;
210 break;
212 new_events = EPOLLERR | EPOLLHUP;
213 break;
214 }
215
216 epoll_event ev{};
217 ev.data.fd = fd;
218 new_events |= EPOLLET;
219
220 if (oneshot) {
221 new_events |= EPOLLONESHOT;
222 }
223
224 auto &b = bucket(fd);
225
226 std::lock_guard<std::mutex> lk(b.mtx_);
227 const auto it = b.interest_.find(fd);
228
229 auto old_events = (it == b.interest_.end()) ? 0 : it->second;
230 auto merged_events = new_events | old_events;
231
232 // the events passed to epoll should only contain IN|OUT
233 ev.events = merged_events & ~kAlwaysEnabledEvents;
234
235 if ((old_events & kAllEvents) == 0) {
236 // no events where registered before, add.
237 const auto ctl_res =
239 if (!ctl_res) return ctl_res;
240 } else {
241 const auto ctl_res =
243 if (!ctl_res) return ctl_res;
244 }
245
246 // the tracked events should contain IN|OUT|ERR|HUP
247 if (it != b.interest_.end()) {
248 it->second = merged_events;
249 } else {
250 b.interest_.emplace(fd, merged_events);
251 }
252
253 return {};
254 }
255
258 auto &b = bucket(fd);
259 // std::cerr << __LINE__ << ": del: " << fd << std::endl;
260
261 std::lock_guard<std::mutex> lk(b.mtx_);
262
263 // may be called from another thread through ->cancel()
264 const auto it = b.interest_.find(fd);
265 if (it != b.interest_.end()) {
266 if ((it->second & kAllEvents) != 0) {
267 auto epoll_ctl_res =
268 impl::epoll::ctl(epfd, impl::epoll::Cmd::del, fd, nullptr);
269 if (!epoll_ctl_res) return epoll_ctl_res;
270 }
271
272 b.interest_.erase(it);
273 } else {
274 // return ENOENT as epoll_ctl() would do
276 make_error_code(std::errc::no_such_file_or_directory));
277 }
278
279 return {};
280 }
281
282 // remove interest for revent from file-descriptor.
284 int epfd, native_handle_type fd, uint32_t revent) {
285 auto &b = bucket(fd);
286
287 std::lock_guard<std::mutex> lk(b.mtx_);
288
289 const auto it = b.interest_.find(fd);
290 if (it == b.interest_.end()) {
291 // return ENOENT as epoll_ctl() would do
293 make_error_code(std::errc::no_such_file_or_directory));
294 }
295
296 // fd is found
297 auto &interest = *it;
298
299 // one-shot-events which fired
300 const auto fd_events = revent & kAllEvents;
301 const auto updated_fd_events = interest.second & ~fd_events;
302
303 if ((updated_fd_events & kSettableEvents) != 0) {
304 epoll_event ev{};
305 ev.data.fd = fd;
306 ev.events = updated_fd_events & ~kAlwaysEnabledEvents;
307
308 const auto ctl_res =
310 if (!ctl_res) return stdx::make_unexpected(ctl_res.error());
311 } else if ((updated_fd_events & kAllEvents) == 0) {
312 const auto ctl_res =
313 impl::epoll::ctl(epfd, impl::epoll::Cmd::del, fd, nullptr);
314 if (!ctl_res) return stdx::make_unexpected(ctl_res.error());
315 }
316
317 interest.second = updated_fd_events;
318
319 return {};
320 }
321
322 /**
323 * update registered fd-interest after a oneshot event fired.
324 */
326 int epfd, native_handle_type fd, uint32_t revent) {
327 auto &b = bucket(fd);
328
329 std::lock_guard<std::mutex> lk(b.mtx_);
330
331 const auto it = b.interest_.find(fd);
332 if (it == b.interest_.end()) {
333 // return ENOENT as epoll_ctl() would do
335 make_error_code(std::errc::no_such_file_or_directory));
336 }
337
338 auto &interest = *it;
339
340 if (!(interest.second & EPOLLONESHOT)) {
341 // not a oneshot event. The interest hasn't changed.
342 return {};
343 }
344
345 // check that the one-shot-events IN and OUT are expected and tracked.
346 //
347 // interest | revent | result
348 // -----------+----------+-------
349 // {} | {IN} | Fail
350 // {} | {OUT} | Fail
351 // {} | {IN,OUT} | Fail
352 // {} | {ERR} | Ok({})
353 // {} | {IN,ERR} | Fail
354 // {IN} | {IN} | Ok({})
355 // {IN} | {OUT} | Fail
356 // {IN} | {IN,OUT} | Fail
357 // {IN} | {ERR} | Ok({IN})
358 // {IN} | {IN,ERR} | Ok({})
359 // {IN,OUT} | {IN} | Ok({OUT})
360 // {IN,OUT} | {OUT} | Ok({IN})
361 // {IN,OUT} | {IN,OUT} | Ok({})
362 // {IN,OUT} | {ERR} | Ok({IN,OUT})
363 // {IN,OUT} | {IN,ERR} | Ok({OUT})
364
365 // events which fired
366 const auto fd_events = revent & kAllEvents;
367
368 // events that we are interested in.
369 const auto fd_interest = interest.second & kAllEvents;
370
371 if (fd_events != 0 && //
372 (fd_events & fd_interest) == 0) {
373 std::cerr << "after_event_fired(" << fd << ", "
374 << std::bitset<32>(fd_events) << ") not in "
375 << std::bitset<32>(fd_interest) << std::endl;
377 make_error_code(std::errc::argument_out_of_domain));
378 }
379
380 // update the fd-interest
381 const auto updated_fd_events = interest.second & ~fd_events;
382
383 if ((updated_fd_events & kSettableEvents) != 0) {
384 // if a one shot event with multiple waiting events fired for one of the
385 // events, it removes all interests for the fd.
386 //
387 // waiting for: IN|OUT
388 // fires: IN
389 // epoll.interesting:0
390 // not fired: OUT
391 //
392 // add back the events that have not fired yet.
393 epoll_event ev{};
394 ev.data.fd = fd;
395 ev.events = updated_fd_events & ~kAlwaysEnabledEvents;
396
397 const auto ctl_res =
399 if (!ctl_res) return stdx::make_unexpected(ctl_res.error());
400 } else if ((updated_fd_events & kAllEvents) == 0) {
401 // no interest anymore.
402 const auto ctl_res =
403 impl::epoll::ctl(epfd, impl::epoll::Cmd::del, fd, nullptr);
404 if (!ctl_res) return stdx::make_unexpected(ctl_res.error());
405 }
406
407 interest.second = updated_fd_events;
408
409 return {};
410 }
411
412 std::optional<int32_t> interest(native_handle_type fd) const {
413 auto &b = bucket(fd);
414
415 std::lock_guard<std::mutex> lk(b.mtx_);
416
417 const auto it = b.interest_.find(fd);
418 if (it != b.interest_.end()) {
419 return it->second;
420 } else {
421 return std::nullopt;
422 }
423 }
424
425 private:
426 // segmented map of fd-to-interest
427 //
428 // allows to split the map and the mutex
430 mutable std::mutex mtx_;
431 std::unordered_map<impl::socket::native_handle_type, uint32_t> interest_;
432 };
433
434 // get locked bucket by file-descriptor.
436 const size_t ndx = fd % buckets_.size();
437
438 return buckets_[ndx];
439 }
440
442 const size_t ndx = fd % buckets_.size();
443
444 return buckets_[ndx];
445 }
446
447 // segment the fd-to-interest map into N buckets
448 std::array<locked_bucket, 101> buckets_;
449 };
450
453 return registered_events_.merge(epfd_, fd, wt, true);
454 }
455
458 return registered_events_.merge(epfd_, fd, wt, false);
459 }
460
462 native_handle_type fd) override {
463 std::lock_guard lk(fd_events_mtx_);
464 auto res = registered_events_.erase(epfd_, fd);
465 if (res) {
466 // remove all events which are already fetched by poll_one()
467 for (size_t ndx = fd_events_processed_; ndx < fd_events_size_;) {
468 auto ev = fd_events_[ndx];
469
470 if (ev.data.fd == fd) {
471 // found one, move it to the end and throw away this one.
472 if (ndx != fd_events_size_ - 1) {
474 }
475
477 } else {
478 ++ndx;
479 }
480 }
481 }
482
483 return res;
484 }
485
487 native_handle_type fd, uint32_t revents) {
488 return registered_events_.remove_fd_interest(epfd_, fd, revents);
489 }
490
491 /**
492 * get current fd-interest.
493 *
494 * @returns fd-interest as bitmask of raw EPOLL* flags
495 */
496 std::optional<int32_t> interest(native_handle_type fd) const {
497 return registered_events_.interest(fd);
498 }
499
501 size_t ndx = fd_events_processed_;
502
503 auto ev = fd_events_[ndx];
504
505 // if there are multiple events:
506 // - OUT before IN.
507 // - IN before ERR|HUP.
508 // - ERR before HUP.
509 short revent{};
510 if (ev.events & EPOLLOUT) {
511 fd_events_[ndx].events &= ~EPOLLOUT;
512 revent = EPOLLOUT;
513 } else if (ev.events & EPOLLIN) {
514 fd_events_[ndx].events &= ~EPOLLIN;
515 revent = EPOLLIN;
516 } else if (ev.events & EPOLLERR) {
517 fd_events_[ndx].events &= ~EPOLLERR;
518 revent = EPOLLERR;
519 } else if (ev.events & EPOLLHUP) {
520 fd_events_[ndx].events &= ~EPOLLHUP;
521 revent = EPOLLHUP;
522 }
523
524 // all interesting events processed, go the next one.
525 if ((fd_events_[ndx].events & (EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP)) ==
526 0) {
528 }
529
530 return fd_event{ev.data.fd, revent};
531 }
532
534 std::chrono::milliseconds timeout) {
535 decltype(fd_events_) evs{};
536
537 auto res = impl::epoll::wait(epfd_, evs.data(), evs.size(), timeout);
538
539 if (!res) return stdx::make_unexpected(res.error());
540
541 std::lock_guard lk(fd_events_mtx_);
542 fd_events_ = evs;
543
545 fd_events_size_ = *res;
546
547 if (fd_events_size_ == 0) {
548 return stdx::make_unexpected(make_error_code(std::errc::timed_out));
549 }
550
551 for (size_t ndx{}; ndx < fd_events_size_; ++ndx) {
552 const ::epoll_event ev = fd_events_[ndx];
553
554 auto after_res = after_event_fired(epfd_, ev.data.fd, ev.events);
555 if (!after_res) {
557 oss << "after_event_fired(" << ev.data.fd << ", "
558 << std::bitset<32>(ev.events) << ") " << after_res.error() << " "
559 << after_res.error().message() << std::endl;
560 std::cerr << oss.str();
561 }
562 }
563
564 return pop_event();
565 }
566
567 /**
568 * poll one event from the registered fd-interest.
569 *
570 * removes the interest of the event that fired
571 *
572 * @param timeout wait at most timeout milliseconds
573 *
574 * @returns fd_event which fired
575 * @retval std::errc::timed_out in case of timeout
576 */
578 std::chrono::milliseconds timeout) override {
579 if (!is_open()) {
581 make_error_code(std::errc::invalid_argument));
582 }
583
584 auto ev_res = [this]() -> stdx::expected<fd_event, std::error_code> {
585 std::lock_guard lk(fd_events_mtx_);
586
588 // no event.
590 make_error_code(std::errc::no_such_file_or_directory));
591 }
592
593 return pop_event();
594 }();
595
596 if (!ev_res) {
597 if (ev_res.error() == std::errc::no_such_file_or_directory) {
598 ev_res = update_fd_events(timeout);
599 }
600
601 if (!ev_res) return stdx::make_unexpected(ev_res.error());
602 }
603
604 auto ev = *ev_res;
605
607 ? (ev.fd == notify_fd_)
608 : (ev.fd == wakeup_fds_.first)) {
609 // wakeup fd fired
610 //
611 // - don't remove the interest for it
612 // - report to the caller that we don't have an event yet by saying we got
613 // interrupted
614 on_notify();
615
617 }
618
619 return ev;
620 }
621
622 private:
624
625 // the event-set should be large enough to get a full picture as we otherwise
626 // might starve connections because we fetch a hot set of fds instead of the
627 // full set
628 //
629 // ready-set = [ 1 2 3 4 5 6 ]
630 //
631 // epoll_wait(.., 4, ...) = [ 1 2 3 4 ]
632 // epoll_ctl(MOD, POLLIN, 1)
633 // epoll_ctl(MOD, POLLIN, 2)
634 // epoll_ctl(MOD, POLLIN, 3)
635 // epoll_ctl(MOD, POLLIN, 4)
636 //
637 // ... 1, 2, 3, 4 may become ready in the meantime
638 //
639 // epoll_wait(.., 4, ...) = [ 1 2 3 4 ]
640 //
641 // ... and 5, 6 never get processed.
642 std::mutex fd_events_mtx_;
643 std::array<epoll_event, 8192> fd_events_{};
647
648 std::pair<impl::file::file_handle_type, impl::file::file_handle_type>
650
652
655 uint32_t revents) {
656 return registered_events_.after_event_fired(epfd, fd, revents);
657 }
658};
659} // namespace net
660
661#endif
662#endif
Definition: io_service_base.h:87
impl::socket::native_handle_type native_handle_type
Definition: io_service_base.h:89
Definition: linux_epoll_io_service.h:198
stdx::expected< void, std::error_code > erase(int epfd, native_handle_type fd)
Definition: linux_epoll_io_service.h:256
stdx::expected< void, std::error_code > merge(int epfd, native_handle_type fd, impl::socket::wait_type wt, bool oneshot)
Definition: linux_epoll_io_service.h:200
std::optional< int32_t > interest(native_handle_type fd) const
Definition: linux_epoll_io_service.h:412
const locked_bucket & bucket(native_handle_type fd) const
Definition: linux_epoll_io_service.h:441
stdx::expected< void, std::error_code > after_event_fired(int epfd, native_handle_type fd, uint32_t revent)
update registered fd-interest after a oneshot event fired.
Definition: linux_epoll_io_service.h:325
std::array< locked_bucket, 101 > buckets_
Definition: linux_epoll_io_service.h:448
stdx::expected< void, std::error_code > remove_fd_interest(int epfd, native_handle_type fd, uint32_t revent)
Definition: linux_epoll_io_service.h:283
locked_bucket & bucket(native_handle_type fd)
Definition: linux_epoll_io_service.h:435
Definition: linux_epoll_io_service.h:57
stdx::expected< fd_event, std::error_code > update_fd_events(std::chrono::milliseconds timeout)
Definition: linux_epoll_io_service.h:533
~linux_epoll_io_service() override
Definition: linux_epoll_io_service.h:66
stdx::expected< void, std::error_code > add_fd_interest(native_handle_type fd, impl::socket::wait_type wt) override
Definition: linux_epoll_io_service.h:451
static constexpr const int kAlwaysEnabledEvents
Definition: linux_epoll_io_service.h:62
impl::file::file_handle_type notify_fd_
Definition: linux_epoll_io_service.h:651
stdx::expected< void, std::error_code > add_fd_interest_permanent(native_handle_type fd, impl::socket::wait_type wt)
Definition: linux_epoll_io_service.h:456
size_t fd_events_size_
Definition: linux_epoll_io_service.h:645
static constexpr const int kSettableEvents
Definition: linux_epoll_io_service.h:61
void notify() override
notify the poll_one() that something may have changed.
Definition: linux_epoll_io_service.h:145
stdx::expected< void, std::error_code > after_event_fired(int epfd, native_handle_type fd, uint32_t revents)
Definition: linux_epoll_io_service.h:653
std::mutex fd_events_mtx_
Definition: linux_epoll_io_service.h:642
bool is_open() const noexcept
Definition: linux_epoll_io_service.h:68
FdInterest registered_events_
Definition: linux_epoll_io_service.h:623
void on_notify()
Definition: linux_epoll_io_service.h:116
impl::file::file_handle_type epfd_
Definition: linux_epoll_io_service.h:646
stdx::expected< fd_event, std::error_code > pop_event()
Definition: linux_epoll_io_service.h:500
stdx::expected< fd_event, std::error_code > poll_one(std::chrono::milliseconds timeout) override
poll one event from the registered fd-interest.
Definition: linux_epoll_io_service.h:577
stdx::expected< void, std::error_code > close()
Definition: linux_epoll_io_service.h:172
static constexpr const int kAllEvents
Definition: linux_epoll_io_service.h:63
stdx::expected< void, std::error_code > open() noexcept override
open the io-service.
Definition: linux_epoll_io_service.h:75
std::array< epoll_event, 8192 > fd_events_
Definition: linux_epoll_io_service.h:643
stdx::expected< void, std::error_code > remove_fd(native_handle_type fd) override
Definition: linux_epoll_io_service.h:461
size_t fd_events_processed_
Definition: linux_epoll_io_service.h:644
stdx::expected< void, std::error_code > remove_fd_interest(native_handle_type fd, uint32_t revents)
Definition: linux_epoll_io_service.h:486
std::optional< int32_t > interest(native_handle_type fd) const
get current fd-interest.
Definition: linux_epoll_io_service.h:496
std::pair< impl::file::file_handle_type, impl::file::file_handle_type > wakeup_fds_
Definition: linux_epoll_io_service.h:649
Definition: expected.h:944
if(!(yy_init))
Definition: lexyy.cc:1144
static bool interrupted
Definition: mysqladmin.cc:66
Definition: buf0block_hint.cc:30
static bool timeout(bool(*wait_condition)())
Timeout function.
Definition: log0meb.cc:496
stdx::expected< void, std::error_code > ctl(int epfd, Cmd cmd, int fd, epoll_event *ev)
Definition: linux_epoll.h:72
stdx::expected< int, std::error_code > create()
Definition: linux_epoll.h:60
stdx::expected< size_t, std::error_code > wait(int epfd, epoll_event *fd_events, size_t num_fd_events, std::chrono::milliseconds timeout)
Definition: linux_epoll.h:83
int file_handle_type
Definition: file.h:53
constexpr file_handle_type kInvalidHandle
Definition: file.h:54
stdx::expected< std::pair< file_handle_type, file_handle_type >, std::error_code > pipe(int flags=0)
create pipe.
Definition: file.h:144
stdx::expected< void, std::error_code > close(file_handle_type native_handle)
close file handle.
Definition: file.h:239
wait_type
Definition: socket_constants.h:86
stdx::expected< bool, error_type > native_non_blocking(native_handle_type native_handle)
Definition: socket.h:106
int native_handle_type
Definition: socket_constants.h:51
Definition: buffer.h:45
std::enable_if_t< is_const_buffer_sequence< ConstBufferSequence >::value, stdx::expected< size_t, std::error_code > > write(SyncWriteStream &stream, const ConstBufferSequence &buffers)
Definition: buffer.h:992
std::enable_if_t< is_mutable_buffer_sequence< MutableBufferSequence >::value, stdx::expected< size_t, std::error_code > > read(SyncReadStream &stream, const MutableBufferSequence &buffers)
Definition: buffer.h:838
std::error_code make_error_code(net::stream_errc e) noexcept
Definition: buffer.h:103
constexpr auto make_unexpected(E &&e) -> unexpected< std::decay_t< E > >
Definition: expected.h:125
std::basic_ostringstream< char, std::char_traits< char >, ut::allocator< char > > ostringstream
Specialization of basic_ostringstream which uses ut::allocator.
Definition: ut0new.h:2870
static void swap(String &a, String &b) noexcept
Definition: sql_string.h:642
Definition: io_service_base.h:69
native_handle_type fd
Definition: io_service_base.h:75
Definition: linux_epoll_io_service.h:429
std::mutex mtx_
Definition: linux_epoll_io_service.h:430
std::unordered_map< impl::socket::native_handle_type, uint32_t > interest_
Definition: linux_epoll_io_service.h:431