1313#include < windows.h>
1414
1515#else // Linux
16+ #include < poll.h>
1617#include < limits.h>
1718#include < sys/inotify.h>
1819#include < unistd.h>
2122class FileWatcherService {
2223 private:
2324#if defined(_WIN32)
24- HANDLE dir_handle;
25+ HANDLE dir_handle = INVALID_HANDLE_VALUE
2526 HANDLE stop_event;
2627#elif defined(__APPLE__)
2728 FSEventStreamRef event_stream;
@@ -34,7 +35,7 @@ class FileWatcherService {
3435 public:
3536 FileWatcherService (const std::string& path,
3637 std::shared_ptr<ModelService> model_service)
37- : watch_path_{path}, running_{false } {
38+ : watch_path_{path}, running_{false }, model_service_{model_service} {
3839 if (!std::filesystem::exists (path)) {
3940 throw std::runtime_error (" Path does not exist: " + path);
4041 }
@@ -75,11 +76,12 @@ class FileWatcherService {
7576 }
7677#else // Linux
7778 // For Linux, closing the fd will interrupt the read() call
79+ CTL_INF (" before close fd!" );
7880 if (fd >= 0 ) {
7981 close (fd);
8082 }
8183#endif
82-
84+ CTL_INF ( " before join! " );
8385 // Add timeout to avoid infinite waiting
8486 if (watch_thread_.joinable ()) {
8587 watch_thread_.join ();
@@ -216,23 +218,36 @@ class FileWatcherService {
216218#else // Linux
217219
218220 void AddWatch (const std::string& dirPath) {
219- wd = inotify_add_watch (fd, dirPath. c_str (),
220- IN_DELETE | IN_CREATE | IN_DELETE_SELF );
221+ const int watch_flags = IN_DELETE | IN_DELETE_SELF | IN_CREATE;
222+ wd = inotify_add_watch (fd, dirPath. c_str (), watch_flags );
221223 if (wd < 0 ) {
222- throw std::runtime_error (" Failed to add watch on: " + dirPath);
224+ throw std::runtime_error (" Failed to add watch on " + dirPath +
225+ " : " + std::string (strerror (errno)));
223226 }
224227 watch_descriptors[wd] = dirPath;
225228
226- // Recursively add watches to subdirectories
227- for (const auto & entry :
228- std::filesystem::recursive_directory_iterator (dirPath)) {
229- if (std::filesystem::is_directory (entry)) {
230- AddWatch (entry.path ().string ());
229+ // Add watches for subdirectories
230+ try {
231+ for (const auto & entry :
232+ std::filesystem::recursive_directory_iterator (dirPath)) {
233+ if (std::filesystem::is_directory (entry)) {
234+ int subwd = inotify_add_watch (fd, entry.path ().c_str (), watch_flags);
235+ if (subwd >= 0 ) {
236+ watch_descriptors[subwd] = entry.path ().string ();
237+ } else {
238+ CTL_ERR (" Failed to add watch for subdirectory " +
239+ entry.path ().string () + " : " +
240+ std::string (strerror (errno)));
241+ }
242+ }
231243 }
244+ } catch (const std::filesystem::filesystem_error& e) {
245+ CTL_ERR (" Error walking directory tree: " + std::string (e.what ()));
232246 }
233247 }
234248
235249 void CleanupWatches () {
250+ CTL_INF (" Cleanup Watches" );
236251 for (const auto & [wd, path] : watch_descriptors) {
237252 inotify_rm_watch (fd, wd);
238253 }
@@ -245,33 +260,88 @@ class FileWatcherService {
245260 }
246261
247262 void WatcherThread () {
248- fd = inotify_init ( );
263+ fd = inotify_init1 (IN_NONBLOCK );
249264 if (fd < 0 ) {
250- throw std::runtime_error (" Failed to initialize inotify" );
265+ CTL_ERR (" Failed to initialize inotify: " + std::string (strerror (errno)));
266+ return ;
251267 }
252268
253- // Add initial watch on the main directory
254- AddWatch (watch_path_);
269+ try {
270+ AddWatch (watch_path_);
271+ } catch (const std::exception& e) {
272+ CTL_ERR (" Failed to add watch: " + std::string (e.what ()));
273+ close (fd);
274+ return ;
275+ }
255276
277+ const int POLL_TIMEOUT_MS = 1000 ; // 1 second timeout
256278 char buffer[4096 ];
279+ struct pollfd pfd = {
280+ .fd = fd,
281+ .events = POLLIN,
282+ .revents = 0
283+ };
284+
257285 while (running_) {
258- int length = read (fd, buffer, sizeof (buffer));
259- if (length < 0 ) {
286+ // Poll will sleep until either:
287+ // 1. Events are available (POLLIN)
288+ // 2. POLL_TIMEOUT_MS milliseconds have elapsed
289+ // 3. An error occurs
290+ int poll_result = poll (&pfd, 1 , POLL_TIMEOUT_MS);
291+
292+ if (poll_result < 0 ) {
293+ if (errno == EINTR) {
294+ // System call was interrupted, just retry
295+ continue ;
296+ }
297+ CTL_ERR (" Poll failed: " + std::string (strerror (errno)));
298+ break ;
299+ }
300+
301+ if (poll_result == 0 ) { // Timeout - no events
302+ // No need to sleep - poll() already waited
260303 continue ;
261304 }
262305
263- int i = 0 ;
264- while (i < length) {
265- struct inotify_event * event = (struct inotify_event *)&buffer[i];
266- if (event->mask & IN_DELETE) {
267- auto deletedPath = watch_descriptors[event->wd ] + " /" + event->name ;
268- model_service_->ForceIndexingModelList ();
306+ if (pfd.revents & POLLERR || pfd.revents & POLLNVAL) {
307+ CTL_ERR (" Poll error on fd" );
308+ break ;
309+ }
310+
311+ // Read all pending events
312+ while (running_) {
313+ int length = read (fd, buffer, sizeof (buffer));
314+ if (length < 0 ) {
315+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
316+ // No more events to read
317+ break ;
318+ }
319+ CTL_ERR (" Read error: " + std::string (strerror (errno)));
320+ break ;
321+ }
322+
323+ if (length == 0 ) {
324+ break ;
325+ }
326+
327+ // Process events
328+ size_t i = 0 ;
329+ while (i < static_cast <size_t >(length)) {
330+ struct inotify_event * event =
331+ reinterpret_cast <struct inotify_event *>(&buffer[i]);
332+
333+ if (event->mask & (IN_DELETE | IN_DELETE_SELF)) {
334+ try {
335+ model_service_->ForceIndexingModelList ();
336+ } catch (const std::exception& e) {
337+ CTL_ERR (" Error processing delete event: " + std::string (e.what ()));
338+ }
339+ }
340+
341+ i += sizeof (struct inotify_event ) + event->len ;
269342 }
270- i += sizeof (struct inotify_event ) + event->len ;
271343 }
272344 }
273-
274- close (fd);
275345 }
276346#endif
277347};
0 commit comments