@@ -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
13101312namespace 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
13131326struct 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
14741486struct 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. */
14911502inline 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
15271546struct 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