Skip to content

Commit 3eb9068

Browse files
author
Will
committed
adapter/inotify: implement an associated event buffer
1 parent f88ec3b commit 3eb9068

3 files changed

Lines changed: 102 additions & 70 deletions

File tree

devel/include/detail/wtr/watcher/adapter/linux/inotify/watch.hpp

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@
1919

2020
namespace detail::wtr::watcher::adapter::inotify {
2121

22+
struct fae {
23+
static constexpr int idx_ulim = 16;
24+
25+
struct {
26+
::wtr::watcher::event ev{};
27+
uint32_t cookie = 0;
28+
} evs[idx_ulim]{};
29+
30+
int idx_rm = 0;
31+
};
32+
2233
// clang-format off
2334
struct ke_in_ev {
2435
/* The maximum length of an inotify
@@ -87,8 +98,7 @@ struct ke_in_ev {
8798
| IN_MOVED_TO;
8899

89100
int fd = -1;
90-
::wtr::watcher::event last_rename_ev{};
91-
uint32_t last_rename_cookie = 0;
101+
struct fae fae{};
92102
using paths = std::unordered_map<int, std::filesystem::path>;
93103
paths dm{};
94104
alignas(inotify_event) char ev_buf[buf_len]{0};
@@ -182,56 +192,63 @@ inline auto peek = [](
182192
};
183193

184194
struct parsed {
185-
::wtr::watcher::event ev;
195+
static constexpr uint16_t err_pending = 1 << 0;
196+
static constexpr uint16_t err_overflow = 1 << 1;
197+
::wtr::watcher::event ev{};
186198
inotify_event* next = nullptr;
199+
uint16_t err = 0;
187200
};
188201

189202
/* Constructs a `parsed` event from an read(2)-populated inotify event buffer.
190203
If there is another `inotify_event` available after `in`, it is returned
191204
along with the `watcher::event`. This can be used to update the caller's
192205
position in the buffer.
193206
194-
If the event is a rename event, and there is no associated event immediately
195-
following the the current event, then `cookie` is set to a unique value
196-
which can be used to associate this event with a future event by the caller.
197-
The cookie is otherwise set to zero, and the event is fully parsed.
198207
Generally, rename events are a pair of adjacent `MOVED_FROM`/`TO` events in
199-
the same buffer. In some rare cases (see issue #89), they are not adjacent,
208+
the same buffer. In some rare cases (see #89, #105), they are not adjacent,
200209
or not in the same buffer. */
201210
inline auto parse_ev = [](
202-
ke_in_ev::paths const& dm,
211+
ke_in_ev& ke,
203212
inotify_event const* const in,
204-
inotify_event const* const tail,
205-
uint32_t* cookie) -> parsed
213+
inotify_event const* const tail) -> parsed
206214
{
207215
using ev = ::wtr::watcher::event;
208216
using ev_pt = enum ev::path_type;
209217
using ev_et = enum ev::effect_type;
210218
auto pathof = [&](inotify_event const* const m)
211-
{ return known_pathof_wd_or_default(dm, m->wd) / m->name; };
212-
auto in_path = pathof(in);
219+
{ return known_pathof_wd_or_default(ke.dm, m->wd) / m->name; };
220+
auto path = pathof(in);
213221
auto pt = in->mask & IN_ISDIR ? ev_pt::dir
214-
: is_symlink(in_path) ? ev_pt::sym_link
222+
: is_symlink(path) ? ev_pt::sym_link
215223
: ev_pt::file;
216224
auto et = in->mask & IN_CREATE ? ev_et::create
217225
: in->mask & IN_DELETE ? ev_et::destroy
218226
: in->mask & IN_MOVE ? ev_et::rename
219227
: in->mask & IN_MODIFY ? ev_et::modify
220228
: ev_et::other;
221-
auto isassoc = [&](auto* a, auto* b) -> bool
222-
{ return b && b->cookie && b->cookie == a->cookie; };
223-
auto isfromto = [&](auto* a, auto* b) -> bool
224-
{ return (a->mask & IN_MOVED_FROM) && (b->mask & IN_MOVED_TO); };
225-
auto one = [&](auto* next) -> parsed
226-
{ return {ev(in_path, et, pt), next}; };
227-
auto assoc = [&](auto* a, auto* b) -> parsed
228-
{ return {ev(ev(pathof(a), et, pt), ev(pathof(b), et, pt)), peek(b, tail)}; };
229229
auto next = peek(in, tail);
230-
*cookie = et == ev_et::rename && ! isassoc(in, next) ? in->cookie : 0;
231-
return ! isassoc(in, next) ? one(next)
232-
: isfromto(in, next) ? assoc(in, next)
233-
: isfromto(next, in) ? assoc(next, in)
234-
: one(next);
230+
/* Non-associated events require no special handling */
231+
if (! in->cookie)
232+
return parsed{{path, et, pt}, next};
233+
/* Fast path for adjacent rename events */
234+
if ((in->mask & IN_MOVED_FROM) && next && (next->mask & IN_MOVED_TO))
235+
return parsed{{{path, et, pt}, {pathof(next), et, pt}}, peek(next, tail)};
236+
/* Try to *take* an associated event from `fae` */
237+
for (int i = 0; i < fae::idx_ulim; i++) {
238+
if (ke.fae.evs[i].cookie == in->cookie) {
239+
ke.fae.idx_rm = i;
240+
ke.fae.evs[i].cookie = 0;
241+
return {{ke.fae.evs[i].ev, {path, et, pt}}, next};
242+
}
243+
}
244+
/* Otherwise, save the current event for later */
245+
auto err = ke.fae.evs[ke.fae.idx_rm].cookie != 0
246+
? parsed::err_overflow
247+
: parsed::err_pending;
248+
auto last_ev = ke.fae.evs[ke.fae.idx_rm].ev;
249+
ke.fae.evs[ke.fae.idx_rm] = {{path, et, pt}, in->cookie};
250+
ke.fae.idx_rm = (ke.fae.idx_rm + 1) % fae::idx_ulim;
251+
return {last_ev, next, err};
235252
};
236253

237254
struct defer_dm_rm_wd {
@@ -354,7 +371,6 @@ inline auto do_ev_recv = [](auto const& cb, sysres& sr) -> result
354371
auto const* in_ev = (inotify_event*)(sr.ke.ev_buf);
355372
auto const* const in_ev_tail = (inotify_event*)(sr.ke.ev_buf + read_len);
356373
unsigned in_ev_c = 0;
357-
uint32_t cookie = 0;
358374
auto dmrm = defer_dm_rm_wd{sr.ke};
359375
while (in_ev && in_ev < in_ev_tail) {
360376
auto in_ev_next = peek(in_ev, in_ev_tail);
@@ -366,17 +382,15 @@ inline auto do_ev_recv = [](auto const& cb, sysres& sr) -> result
366382
else if (msk & IN_Q_OVERFLOW)
367383
send_msg(result::w_sys_q_overflow, "", cb);
368384
else if (is_real_event(msk)) {
369-
auto parsed = parse_ev(sr.ke.dm, in_ev, in_ev_tail, &cookie);
370-
if (cookie && sr.ke.last_rename_cookie != cookie)
371-
sr.ke.last_rename_ev = parsed.ev, sr.ke.last_rename_cookie = cookie;
372-
else if (cookie)
373-
cb({sr.ke.last_rename_ev, parse_ev(sr.ke.dm, in_ev, in_ev_tail, &cookie).ev});
374-
else if (msk & IN_ISDIR && msk & IN_CREATE)
385+
auto parsed = parse_ev(sr.ke, in_ev, in_ev_tail);
386+
if (msk & IN_ISDIR && msk & IN_CREATE)
375387
walkdir_do(parsed.ev.path_name.c_str(), [&](auto dir) {
376388
do_mark(dir, sr.ke.fd, sr.ke.dm, cb);
377389
cb({dir, parsed.ev.effect_type, parsed.ev.path_type});
378390
});
379-
else
391+
if (parsed.err & parsed::err_overflow)
392+
send_msg(result::w_self_q_overflow, parsed.ev.path_name.c_str(), cb);
393+
if (! (parsed.err & parsed::err_pending))
380394
cb(parsed.ev);
381395
in_ev_next = parsed.next;
382396
}

devel/include/detail/wtr/watcher/adapter/linux/sysres.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ enum class result : unsigned short {
3434
w_sys_bad_fd,
3535
w_sys_bad_meta,
3636
w_sys_q_overflow,
37+
w_self_q_overflow,
3738
complete,
3839
e,
3940
e_sys_api_inotify,
@@ -82,6 +83,7 @@ inline constexpr auto to_str(result r)
8283
case result::w_sys_bad_fd: return "w/sys/bad_fd@";
8384
case result::w_sys_bad_meta: return "w/sys/bad_meta@";
8485
case result::w_sys_q_overflow: return "w/sys/q_overflow@";
86+
case result::w_self_q_overflow: return "w/self/q_overflow@";
8587
case result::complete: return "complete@";
8688
case result::e: return "e@";
8789
case result::e_sys_api_inotify: return "e/sys/api/inotify@";

include/wtr/watcher.hpp

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ enum class result : unsigned short {
857857
w_sys_bad_fd,
858858
w_sys_bad_meta,
859859
w_sys_q_overflow,
860+
w_self_q_overflow,
860861
complete,
861862
e,
862863
e_sys_api_inotify,
@@ -905,6 +906,7 @@ inline constexpr auto to_str(result r)
905906
case result::w_sys_bad_fd: return "w/sys/bad_fd@";
906907
case result::w_sys_bad_meta: return "w/sys/bad_meta@";
907908
case result::w_sys_q_overflow: return "w/sys/q_overflow@";
909+
case result::w_self_q_overflow: return "w/self/q_overflow@";
908910
case result::complete: return "complete@";
909911
case result::e: return "e@";
910912
case result::e_sys_api_inotify: return "e/sys/api/inotify@";
@@ -1309,6 +1311,17 @@ inline auto do_ev_recv = [](auto const& cb, sysres& sr) -> result
13091311

13101312
namespace detail::wtr::watcher::adapter::inotify {
13111313

1314+
struct fae {
1315+
static constexpr int idx_ulim = 16;
1316+
1317+
struct {
1318+
::wtr::watcher::event ev{};
1319+
uint32_t cookie = 0;
1320+
} evs[idx_ulim]{};
1321+
1322+
int idx_rm = 0;
1323+
};
1324+
13121325
// clang-format off
13131326
struct ke_in_ev {
13141327
/* The maximum length of an inotify
@@ -1377,8 +1390,7 @@ struct ke_in_ev {
13771390
| IN_MOVED_TO;
13781391

13791392
int fd = -1;
1380-
::wtr::watcher::event last_rename_ev{};
1381-
uint32_t last_rename_cookie = 0;
1393+
struct fae fae{};
13821394
using paths = std::unordered_map<int, std::filesystem::path>;
13831395
paths dm{};
13841396
alignas(inotify_event) char ev_buf[buf_len]{0};
@@ -1472,56 +1484,63 @@ inline auto peek = [](
14721484
};
14731485

14741486
struct parsed {
1475-
::wtr::watcher::event ev;
1487+
static constexpr uint16_t err_pending = 1 << 0;
1488+
static constexpr uint16_t err_overflow = 1 << 1;
1489+
::wtr::watcher::event ev{};
14761490
inotify_event* next = nullptr;
1491+
uint16_t err = 0;
14771492
};
14781493

14791494
/* Constructs a `parsed` event from an read(2)-populated inotify event buffer.
14801495
If there is another `inotify_event` available after `in`, it is returned
14811496
along with the `watcher::event`. This can be used to update the caller's
14821497
position in the buffer.
14831498
1484-
If the event is a rename event, and there is no associated event immediately
1485-
following the the current event, then `cookie` is set to a unique value
1486-
which can be used to associate this event with a future event by the caller.
1487-
The cookie is otherwise set to zero, and the event is fully parsed.
14881499
Generally, rename events are a pair of adjacent `MOVED_FROM`/`TO` events in
1489-
the same buffer. In some rare cases (see issue #89), they are not adjacent,
1500+
the same buffer. In some rare cases (see #89, #105), they are not adjacent,
14901501
or not in the same buffer. */
14911502
inline auto parse_ev = [](
1492-
ke_in_ev::paths const& dm,
1503+
ke_in_ev& ke,
14931504
inotify_event const* const in,
1494-
inotify_event const* const tail,
1495-
uint32_t* cookie) -> parsed
1505+
inotify_event const* const tail) -> parsed
14961506
{
14971507
using ev = ::wtr::watcher::event;
14981508
using ev_pt = enum ev::path_type;
14991509
using ev_et = enum ev::effect_type;
15001510
auto pathof = [&](inotify_event const* const m)
1501-
{ return known_pathof_wd_or_default(dm, m->wd) / m->name; };
1502-
auto in_path = pathof(in);
1511+
{ return known_pathof_wd_or_default(ke.dm, m->wd) / m->name; };
1512+
auto path = pathof(in);
15031513
auto pt = in->mask & IN_ISDIR ? ev_pt::dir
1504-
: is_symlink(in_path) ? ev_pt::sym_link
1514+
: is_symlink(path) ? ev_pt::sym_link
15051515
: ev_pt::file;
15061516
auto et = in->mask & IN_CREATE ? ev_et::create
15071517
: in->mask & IN_DELETE ? ev_et::destroy
15081518
: in->mask & IN_MOVE ? ev_et::rename
15091519
: in->mask & IN_MODIFY ? ev_et::modify
15101520
: ev_et::other;
1511-
auto isassoc = [&](auto* a, auto* b) -> bool
1512-
{ return b && b->cookie && b->cookie == a->cookie; };
1513-
auto isfromto = [&](auto* a, auto* b) -> bool
1514-
{ return (a->mask & IN_MOVED_FROM) && (b->mask & IN_MOVED_TO); };
1515-
auto one = [&](auto* next) -> parsed
1516-
{ return {ev(in_path, et, pt), next}; };
1517-
auto assoc = [&](auto* a, auto* b) -> parsed
1518-
{ return {ev(ev(pathof(a), et, pt), ev(pathof(b), et, pt)), peek(b, tail)}; };
15191521
auto next = peek(in, tail);
1520-
*cookie = et == ev_et::rename && ! isassoc(in, next) ? in->cookie : 0;
1521-
return ! isassoc(in, next) ? one(next)
1522-
: isfromto(in, next) ? assoc(in, next)
1523-
: isfromto(next, in) ? assoc(next, in)
1524-
: one(next);
1522+
/* Non-associated events require no special handling */
1523+
if (! in->cookie)
1524+
return parsed{{path, et, pt}, next};
1525+
/* Fast path for adjacent rename events */
1526+
if ((in->mask & IN_MOVED_FROM) && next && (next->mask & IN_MOVED_TO))
1527+
return parsed{{{path, et, pt}, {pathof(next), et, pt}}, peek(next, tail)};
1528+
/* Try to *take* an associated event from `fae` */
1529+
for (int i = 0; i < fae::idx_ulim; i++) {
1530+
if (ke.fae.evs[i].cookie == in->cookie) {
1531+
ke.fae.idx_rm = i;
1532+
ke.fae.evs[i].cookie = 0;
1533+
return {{ke.fae.evs[i].ev, {path, et, pt}}, next};
1534+
}
1535+
}
1536+
/* Otherwise, save the current event for later */
1537+
auto err = ke.fae.evs[ke.fae.idx_rm].cookie != 0
1538+
? parsed::err_overflow
1539+
: parsed::err_pending;
1540+
auto last_ev = ke.fae.evs[ke.fae.idx_rm].ev;
1541+
ke.fae.evs[ke.fae.idx_rm] = {{path, et, pt}, in->cookie};
1542+
ke.fae.idx_rm = (ke.fae.idx_rm + 1) % fae::idx_ulim;
1543+
return {last_ev, next, err};
15251544
};
15261545

15271546
struct defer_dm_rm_wd {
@@ -1644,7 +1663,6 @@ inline auto do_ev_recv = [](auto const& cb, sysres& sr) -> result
16441663
auto const* in_ev = (inotify_event*)(sr.ke.ev_buf);
16451664
auto const* const in_ev_tail = (inotify_event*)(sr.ke.ev_buf + read_len);
16461665
unsigned in_ev_c = 0;
1647-
uint32_t cookie = 0;
16481666
auto dmrm = defer_dm_rm_wd{sr.ke};
16491667
while (in_ev && in_ev < in_ev_tail) {
16501668
auto in_ev_next = peek(in_ev, in_ev_tail);
@@ -1656,17 +1674,15 @@ inline auto do_ev_recv = [](auto const& cb, sysres& sr) -> result
16561674
else if (msk & IN_Q_OVERFLOW)
16571675
send_msg(result::w_sys_q_overflow, "", cb);
16581676
else if (is_real_event(msk)) {
1659-
auto parsed = parse_ev(sr.ke.dm, in_ev, in_ev_tail, &cookie);
1660-
if (cookie && sr.ke.last_rename_cookie != cookie)
1661-
sr.ke.last_rename_ev = parsed.ev, sr.ke.last_rename_cookie = cookie;
1662-
else if (cookie)
1663-
cb({sr.ke.last_rename_ev, parse_ev(sr.ke.dm, in_ev, in_ev_tail, &cookie).ev});
1664-
else if (msk & IN_ISDIR && msk & IN_CREATE)
1677+
auto parsed = parse_ev(sr.ke, in_ev, in_ev_tail);
1678+
if (msk & IN_ISDIR && msk & IN_CREATE)
16651679
walkdir_do(parsed.ev.path_name.c_str(), [&](auto dir) {
16661680
do_mark(dir, sr.ke.fd, sr.ke.dm, cb);
16671681
cb({dir, parsed.ev.effect_type, parsed.ev.path_type});
16681682
});
1669-
else
1683+
if (parsed.err & parsed::err_overflow)
1684+
send_msg(result::w_self_q_overflow, parsed.ev.path_name.c_str(), cb);
1685+
if (! (parsed.err & parsed::err_pending))
16701686
cb(parsed.ev);
16711687
in_ev_next = parsed.next;
16721688
}

0 commit comments

Comments
 (0)