brintos

brintos / llvm-project-archived public Read only

0
0
Text · 11.5 KiB · 440b563 Raw
355 lines · cpp
1//===- DirectoryWatcher-linux.cpp - Linux-platform directory watching -----===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "DirectoryScanner.h"10#include "clang/DirectoryWatcher/DirectoryWatcher.h"11 12#include "llvm/ADT/ScopeExit.h"13#include "llvm/Support/Errno.h"14#include "llvm/Support/Error.h"15#include <condition_variable>16#include <mutex>17#include <queue>18#include <string>19#include <thread>20 21#include <fcntl.h>22#include <limits.h>23#include <optional>24#include <sys/epoll.h>25#include <sys/inotify.h>26#include <unistd.h>27 28namespace {29 30using namespace llvm;31using namespace clang;32 33/// Pipe for inter-thread synchronization - for epoll-ing on multiple34/// conditions. It is meant for uni-directional 1:1 signalling - specifically:35/// no multiple consumers, no data passing. Thread waiting for signal should36/// poll the FDRead. Signalling thread should call signal() which writes single37/// character to FDRead.38struct SemaphorePipe {39  // Expects two file-descriptors opened as a pipe in the canonical POSIX40  // order: pipefd[0] refers to the read end of the pipe. pipefd[1] refers to41  // the write end of the pipe.42  SemaphorePipe(int pipefd[2])43      : FDRead(pipefd[0]), FDWrite(pipefd[1]), OwnsFDs(true) {}44  SemaphorePipe(const SemaphorePipe &) = delete;45  void operator=(const SemaphorePipe &) = delete;46  SemaphorePipe(SemaphorePipe &&other)47      : FDRead(other.FDRead), FDWrite(other.FDWrite),48        OwnsFDs(other.OwnsFDs) // Someone could have moved from the other49                               // instance before.50  {51    other.OwnsFDs = false;52  };53 54  void signal() {55#ifndef NDEBUG56    ssize_t Result =57#endif58    llvm::sys::RetryAfterSignal(-1, write, FDWrite, "A", 1);59    assert(Result != -1);60  }61  ~SemaphorePipe() {62    if (OwnsFDs) {63      close(FDWrite);64      close(FDRead);65    }66  }67  const int FDRead;68  const int FDWrite;69  bool OwnsFDs;70 71  static std::optional<SemaphorePipe> create() {72    int InotifyPollingStopperFDs[2];73    if (pipe2(InotifyPollingStopperFDs, O_CLOEXEC) == -1)74      return std::nullopt;75    return SemaphorePipe(InotifyPollingStopperFDs);76  }77};78 79/// Mutex-protected queue of Events.80class EventQueue {81  std::mutex Mtx;82  std::condition_variable NonEmpty;83  std::queue<DirectoryWatcher::Event> Events;84 85public:86  void push_back(const DirectoryWatcher::Event::EventKind K,87                 StringRef Filename) {88    {89      std::unique_lock<std::mutex> L(Mtx);90      Events.emplace(K, Filename);91    }92    NonEmpty.notify_one();93  }94 95  // Blocks on caller thread and uses codition_variable to wait until there's an96  // event to return.97  DirectoryWatcher::Event pop_front_blocking() {98    std::unique_lock<std::mutex> L(Mtx);99    while (true) {100      // Since we might have missed all the prior notifications on NonEmpty we101      // have to check the queue first (under lock).102      if (!Events.empty()) {103        DirectoryWatcher::Event Front = Events.front();104        Events.pop();105        return Front;106      }107      NonEmpty.wait(L, [this]() { return !Events.empty(); });108    }109  }110};111 112class DirectoryWatcherLinux : public clang::DirectoryWatcher {113public:114  DirectoryWatcherLinux(115      llvm::StringRef WatchedDirPath,116      std::function<void(llvm::ArrayRef<Event>, bool)> Receiver,117      bool WaitForInitialSync, int InotifyFD, int InotifyWD,118      SemaphorePipe &&InotifyPollingStopSignal);119 120  ~DirectoryWatcherLinux() override {121    StopWork();122    InotifyPollingThread.join();123    EventsReceivingThread.join();124    inotify_rm_watch(InotifyFD, InotifyWD);125    llvm::sys::RetryAfterSignal(-1, close, InotifyFD);126  }127 128private:129  const std::string WatchedDirPath;130  // inotify file descriptor131  int InotifyFD = -1;132  // inotify watch descriptor133  int InotifyWD = -1;134 135  EventQueue Queue;136 137  // Make sure lifetime of Receiver fully contains lifetime of138  // EventsReceivingThread.139  std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;140 141  // Consumes inotify events and pushes directory watcher events to the Queue.142  void InotifyPollingLoop();143  std::thread InotifyPollingThread;144  // Using pipe so we can epoll two file descriptors at once - inotify and145  // stopping condition.146  SemaphorePipe InotifyPollingStopSignal;147 148  // Does the initial scan of the directory - directly calling Receiver,149  // bypassing the Queue. Both InitialScan and EventReceivingLoop use Receiver150  // which isn't necessarily thread-safe.151  void InitialScan();152 153  // Processing events from the Queue.154  // In case client doesn't want to do the initial scan synchronously155  // (WaitForInitialSync=false in ctor) we do the initial scan at the beginning156  // of this thread.157  std::thread EventsReceivingThread;158  // Push event of WatcherGotInvalidated kind to the Queue to stop the loop.159  // Both InitialScan and EventReceivingLoop use Receiver which isn't160  // necessarily thread-safe.161  void EventReceivingLoop();162 163  // Stops all the async work. Reentrant.164  void StopWork() {165    Queue.push_back(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,166                    "");167    InotifyPollingStopSignal.signal();168  }169};170 171void DirectoryWatcherLinux::InotifyPollingLoop() {172  // We want to be able to read ~30 events at once even in the worst case173  // (obscenely long filenames).174  constexpr size_t EventBufferLength =175      30 * (sizeof(struct inotify_event) + NAME_MAX + 1);176  // http://man7.org/linux/man-pages/man7/inotify.7.html177  // Some systems cannot read integer variables if they are not178  // properly aligned. On other systems, incorrect alignment may179  // decrease performance. Hence, the buffer used for reading from180  // the inotify file descriptor should have the same alignment as181  // struct inotify_event.182 183  struct Buffer {184    alignas(struct inotify_event) char buffer[EventBufferLength];185  };186  auto ManagedBuffer = std::make_unique<Buffer>();187  char *const Buf = ManagedBuffer->buffer;188 189  const int EpollFD = epoll_create1(EPOLL_CLOEXEC);190  if (EpollFD == -1) {191    StopWork();192    return;193  }194  auto EpollFDGuard = llvm::make_scope_exit([EpollFD]() { close(EpollFD); });195 196  struct epoll_event EventSpec;197  EventSpec.events = EPOLLIN;198  EventSpec.data.fd = InotifyFD;199  if (epoll_ctl(EpollFD, EPOLL_CTL_ADD, InotifyFD, &EventSpec) == -1) {200    StopWork();201    return;202  }203 204  EventSpec.data.fd = InotifyPollingStopSignal.FDRead;205  if (epoll_ctl(EpollFD, EPOLL_CTL_ADD, InotifyPollingStopSignal.FDRead,206                &EventSpec) == -1) {207    StopWork();208    return;209  }210 211  std::array<struct epoll_event, 2> EpollEventBuffer;212 213  while (true) {214    const int EpollWaitResult = llvm::sys::RetryAfterSignal(215        -1, epoll_wait, EpollFD, EpollEventBuffer.data(),216        EpollEventBuffer.size(), /*timeout=*/-1 /*== infinity*/);217    if (EpollWaitResult == -1) {218      StopWork();219      return;220    }221 222    // Multiple epoll_events can be received for a single file descriptor per223    // epoll_wait call.224    for (int i = 0; i < EpollWaitResult; ++i) {225      if (EpollEventBuffer[i].data.fd == InotifyPollingStopSignal.FDRead) {226        StopWork();227        return;228      }229    }230 231    // epoll_wait() always return either error or >0 events. Since there was no232    // event for stopping, it must be an inotify event ready for reading.233    ssize_t NumRead = llvm::sys::RetryAfterSignal(-1, read, InotifyFD, Buf,234                                                  EventBufferLength);235    for (char *P = Buf; P < Buf + NumRead;) {236      if (P + sizeof(struct inotify_event) > Buf + NumRead) {237        StopWork();238        llvm_unreachable("an incomplete inotify_event was read");239        return;240      }241 242      struct inotify_event *Event = reinterpret_cast<struct inotify_event *>(P);243      P += sizeof(struct inotify_event) + Event->len;244 245      if (Event->mask & (IN_CREATE | IN_MODIFY | IN_MOVED_TO | IN_DELETE) &&246          Event->len <= 0) {247        StopWork();248        llvm_unreachable("expected a filename from inotify");249        return;250      }251 252      if (Event->mask & (IN_CREATE | IN_MOVED_TO | IN_MODIFY)) {253        Queue.push_back(DirectoryWatcher::Event::EventKind::Modified,254                        Event->name);255      } else if (Event->mask & (IN_DELETE | IN_MOVED_FROM)) {256        Queue.push_back(DirectoryWatcher::Event::EventKind::Removed,257                        Event->name);258      } else if (Event->mask & (IN_DELETE_SELF | IN_MOVE_SELF)) {259        Queue.push_back(DirectoryWatcher::Event::EventKind::WatchedDirRemoved,260                        "");261        StopWork();262        return;263      } else if (Event->mask & IN_IGNORED) {264        StopWork();265        return;266      } else {267        StopWork();268        llvm_unreachable("Unknown event type.");269        return;270      }271    }272  }273}274 275void DirectoryWatcherLinux::InitialScan() {276  this->Receiver(getAsFileEvents(scanDirectory(WatchedDirPath)),277                 /*IsInitial=*/true);278}279 280void DirectoryWatcherLinux::EventReceivingLoop() {281  while (true) {282    DirectoryWatcher::Event Event = this->Queue.pop_front_blocking();283    this->Receiver(Event, false);284    if (Event.Kind ==285        DirectoryWatcher::Event::EventKind::WatcherGotInvalidated) {286      StopWork();287      return;288    }289  }290}291 292DirectoryWatcherLinux::DirectoryWatcherLinux(293    StringRef WatchedDirPath,294    std::function<void(llvm::ArrayRef<Event>, bool)> Receiver,295    bool WaitForInitialSync, int InotifyFD, int InotifyWD,296    SemaphorePipe &&InotifyPollingStopSignal)297    : WatchedDirPath(WatchedDirPath), InotifyFD(InotifyFD),298      InotifyWD(InotifyWD), Receiver(Receiver),299      InotifyPollingStopSignal(std::move(InotifyPollingStopSignal)) {300 301  InotifyPollingThread = std::thread([this]() { InotifyPollingLoop(); });302  // We have no guarantees about thread safety of the Receiver which is being303  // used in both InitialScan and EventReceivingLoop. We shouldn't run these304  // only synchronously.305  if (WaitForInitialSync) {306    InitialScan();307    EventsReceivingThread = std::thread([this]() { EventReceivingLoop(); });308  } else {309    EventsReceivingThread = std::thread([this]() {310      // FIXME: We might want to terminate an async initial scan early in case311      // of a failure in EventsReceivingThread.312      InitialScan();313      EventReceivingLoop();314    });315  }316}317 318} // namespace319 320llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create(321    StringRef Path,322    std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,323    bool WaitForInitialSync) {324  if (Path.empty())325    llvm::report_fatal_error(326        "DirectoryWatcher::create can not accept an empty Path.");327 328  const int InotifyFD = inotify_init1(IN_CLOEXEC);329  if (InotifyFD == -1)330    return llvm::make_error<llvm::StringError>(331        llvm::errnoAsErrorCode(), std::string(": inotify_init1()"));332 333  const int InotifyWD = inotify_add_watch(334      InotifyFD, Path.str().c_str(),335      IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY |336      IN_MOVED_FROM | IN_MOVE_SELF | IN_MOVED_TO | IN_ONLYDIR | IN_IGNORED337#ifdef IN_EXCL_UNLINK338      | IN_EXCL_UNLINK339#endif340      );341  if (InotifyWD == -1)342    return llvm::make_error<llvm::StringError>(343        llvm::errnoAsErrorCode(), std::string(": inotify_add_watch()"));344 345  auto InotifyPollingStopper = SemaphorePipe::create();346 347  if (!InotifyPollingStopper)348    return llvm::make_error<llvm::StringError>(349        llvm::errnoAsErrorCode(), std::string(": SemaphorePipe::create()"));350 351  return std::make_unique<DirectoryWatcherLinux>(352      Path, Receiver, WaitForInitialSync, InotifyFD, InotifyWD,353      std::move(*InotifyPollingStopper));354}355