Skip to content

Commit a153c00

Browse files
author
git apple-llvm automerger
committed
Merge commit 'd38d7b6c9ed0' from apple/stable/20200714 into swift/main
2 parents c377dba + d38d7b6 commit a153c00

File tree

2 files changed

+260
-20
lines changed

2 files changed

+260
-20
lines changed

clang/lib/DirectoryWatcher/windows/DirectoryWatcher-windows.cpp

Lines changed: 259 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,12 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9-
// TODO: This is not yet an implementation, but it will make it so Windows
10-
// builds don't fail.
11-
129
#include "DirectoryScanner.h"
1310
#include "clang/DirectoryWatcher/DirectoryWatcher.h"
14-
1511
#include "llvm/ADT/STLExtras.h"
16-
#include "llvm/ADT/ScopeExit.h"
17-
#include "llvm/Support/AlignOf.h"
18-
#include "llvm/Support/Errno.h"
19-
#include "llvm/Support/Mutex.h"
12+
#include "llvm/Support/ConvertUTF.h"
2013
#include "llvm/Support/Path.h"
21-
#include <atomic>
14+
#include "llvm/Support/Windows/WindowsSupport.h"
2215
#include <condition_variable>
2316
#include <mutex>
2417
#include <queue>
@@ -28,23 +21,270 @@
2821

2922
namespace {
3023

24+
using DirectoryWatcherCallback =
25+
std::function<void(llvm::ArrayRef<clang::DirectoryWatcher::Event>, bool)>;
26+
3127
using namespace llvm;
3228
using namespace clang;
3329

3430
class DirectoryWatcherWindows : public clang::DirectoryWatcher {
31+
OVERLAPPED Overlapped;
32+
33+
std::vector<DWORD> Notifications;
34+
35+
std::thread WatcherThread;
36+
std::thread HandlerThread;
37+
std::function<void(ArrayRef<DirectoryWatcher::Event>, bool)> Callback;
38+
SmallString<MAX_PATH> Path;
39+
HANDLE Terminate;
40+
41+
class EventQueue {
42+
std::mutex M;
43+
std::queue<DirectoryWatcher::Event> Q;
44+
std::condition_variable CV;
45+
46+
public:
47+
void emplace(DirectoryWatcher::Event::EventKind Kind, StringRef Path) {
48+
{
49+
std::unique_lock<std::mutex> L(M);
50+
Q.emplace(Kind, Path);
51+
}
52+
CV.notify_one();
53+
}
54+
55+
DirectoryWatcher::Event pop_front() {
56+
std::unique_lock<std::mutex> L(M);
57+
while (true) {
58+
if (!Q.empty()) {
59+
DirectoryWatcher::Event E = Q.front();
60+
Q.pop();
61+
return E;
62+
}
63+
CV.wait(L, [this]() { return !Q.empty(); });
64+
}
65+
}
66+
} Q;
67+
3568
public:
36-
~DirectoryWatcherWindows() override { }
37-
void InitialScan() { }
38-
void EventReceivingLoop() { }
39-
void StopWork() { }
69+
DirectoryWatcherWindows(HANDLE DirectoryHandle, bool WaitForInitialSync,
70+
DirectoryWatcherCallback Receiver);
71+
72+
~DirectoryWatcherWindows() override;
73+
74+
void InitialScan();
75+
void WatcherThreadProc(HANDLE DirectoryHandle);
76+
void NotifierThreadProc(bool WaitForInitialSync);
4077
};
78+
79+
DirectoryWatcherWindows::DirectoryWatcherWindows(
80+
HANDLE DirectoryHandle, bool WaitForInitialSync,
81+
DirectoryWatcherCallback Receiver)
82+
: Callback(Receiver), Terminate(INVALID_HANDLE_VALUE) {
83+
// Pre-compute the real location as we will be handing over the directory
84+
// handle to the watcher and performing synchronous operations.
85+
{
86+
DWORD Length = GetFinalPathNameByHandleW(DirectoryHandle, NULL, 0, 0);
87+
88+
std::vector<WCHAR> Buffer;
89+
Buffer.resize(Length);
90+
91+
Length = GetFinalPathNameByHandleW(DirectoryHandle, Buffer.data(),
92+
Buffer.size(), 0);
93+
Buffer.resize(Length);
94+
95+
llvm::sys::windows::UTF16ToUTF8(Buffer.data(), Buffer.size(), Path);
96+
}
97+
98+
Notifications.resize(4 * (sizeof(FILE_NOTIFY_INFORMATION) +
99+
MAX_PATH * sizeof(WCHAR)));
100+
101+
memset(&Overlapped, 0, sizeof(Overlapped));
102+
Overlapped.hEvent =
103+
CreateEventW(NULL, /*bManualReset=*/TRUE, /*bInitialState=*/FALSE, NULL);
104+
assert(Overlapped.hEvent && "unable to create event");
105+
106+
Terminate = CreateEventW(NULL, /*bManualReset=*/TRUE,
107+
/*bInitialState=*/FALSE, NULL);
108+
109+
WatcherThread = std::thread([this, DirectoryHandle]() {
110+
this->WatcherThreadProc(DirectoryHandle);
111+
});
112+
113+
if (WaitForInitialSync)
114+
InitialScan();
115+
116+
HandlerThread = std::thread([this, WaitForInitialSync]() {
117+
this->NotifierThreadProc(WaitForInitialSync);
118+
});
119+
}
120+
121+
DirectoryWatcherWindows::~DirectoryWatcherWindows() {
122+
// Signal the Watcher to exit.
123+
SetEvent(Terminate);
124+
HandlerThread.join();
125+
WatcherThread.join();
126+
CloseHandle(Terminate);
127+
CloseHandle(Overlapped.hEvent);
128+
}
129+
130+
void DirectoryWatcherWindows::InitialScan() {
131+
Callback(getAsFileEvents(scanDirectory(Path.data())), /*IsInitial=*/true);
132+
}
133+
134+
void DirectoryWatcherWindows::WatcherThreadProc(HANDLE DirectoryHandle) {
135+
while (true) {
136+
// We do not guarantee subdirectories, but macOS already provides
137+
// subdirectories, might as well as ...
138+
BOOL WatchSubtree = TRUE;
139+
DWORD NotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME
140+
| FILE_NOTIFY_CHANGE_DIR_NAME
141+
| FILE_NOTIFY_CHANGE_SIZE
142+
| FILE_NOTIFY_CHANGE_LAST_ACCESS
143+
| FILE_NOTIFY_CHANGE_LAST_WRITE
144+
| FILE_NOTIFY_CHANGE_CREATION;
145+
146+
DWORD BytesTransferred;
147+
if (!ReadDirectoryChangesW(DirectoryHandle, Notifications.data(),
148+
Notifications.size(), WatchSubtree,
149+
NotifyFilter, &BytesTransferred, &Overlapped,
150+
NULL)) {
151+
Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,
152+
"");
153+
break;
154+
}
155+
156+
HANDLE Handles[2] = { Terminate, Overlapped.hEvent };
157+
switch (WaitForMultipleObjects(2, Handles, FALSE, INFINITE)) {
158+
case WAIT_OBJECT_0: // Terminate Request
159+
case WAIT_FAILED: // Failure
160+
Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,
161+
"");
162+
(void)CloseHandle(DirectoryHandle);
163+
return;
164+
case WAIT_TIMEOUT: // Spurious wakeup?
165+
continue;
166+
case WAIT_OBJECT_0 + 1: // Directory change
167+
break;
168+
}
169+
170+
if (!GetOverlappedResult(DirectoryHandle, &Overlapped, &BytesTransferred,
171+
FALSE)) {
172+
Q.emplace(DirectoryWatcher::Event::EventKind::WatchedDirRemoved,
173+
"");
174+
Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,
175+
"");
176+
break;
177+
}
178+
179+
// There was a buffer underrun on the kernel side. We may have lost
180+
// events, please re-synchronize.
181+
if (BytesTransferred == 0) {
182+
Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,
183+
"");
184+
break;
185+
}
186+
187+
for (FILE_NOTIFY_INFORMATION *I =
188+
(FILE_NOTIFY_INFORMATION *)Notifications.data();
189+
I;
190+
I = I->NextEntryOffset
191+
? (FILE_NOTIFY_INFORMATION *)((CHAR *)I + I->NextEntryOffset)
192+
: NULL) {
193+
DirectoryWatcher::Event::EventKind Kind =
194+
DirectoryWatcher::Event::EventKind::WatcherGotInvalidated;
195+
switch (I->Action) {
196+
case FILE_ACTION_MODIFIED:
197+
Kind = DirectoryWatcher::Event::EventKind::Modified;
198+
break;
199+
case FILE_ACTION_ADDED:
200+
Kind = DirectoryWatcher::Event::EventKind::Modified;
201+
break;
202+
case FILE_ACTION_REMOVED:
203+
Kind = DirectoryWatcher::Event::EventKind::Removed;
204+
break;
205+
case FILE_ACTION_RENAMED_OLD_NAME:
206+
Kind = DirectoryWatcher::Event::EventKind::Removed;
207+
break;
208+
case FILE_ACTION_RENAMED_NEW_NAME:
209+
Kind = DirectoryWatcher::Event::EventKind::Modified;
210+
break;
211+
}
212+
213+
SmallString<MAX_PATH> filename;
214+
sys::windows::UTF16ToUTF8(I->FileName, I->FileNameLength / 2,
215+
filename);
216+
Q.emplace(Kind, filename);
217+
}
218+
}
219+
220+
(void)CloseHandle(DirectoryHandle);
221+
}
222+
223+
void DirectoryWatcherWindows::NotifierThreadProc(bool WaitForInitialSync) {
224+
// If we did not wait for the initial sync, then we should perform the
225+
// scan when we enter the thread.
226+
if (!WaitForInitialSync)
227+
this->InitialScan();
228+
229+
while (true) {
230+
DirectoryWatcher::Event E = Q.pop_front();
231+
Callback(E, /*IsInitial=*/false);
232+
if (E.Kind == DirectoryWatcher::Event::EventKind::WatcherGotInvalidated)
233+
break;
234+
}
235+
}
236+
237+
auto error(DWORD ErrorCode) {
238+
DWORD Flags = FORMAT_MESSAGE_ALLOCATE_BUFFER
239+
| FORMAT_MESSAGE_FROM_SYSTEM
240+
| FORMAT_MESSAGE_IGNORE_INSERTS;
241+
242+
LPSTR Buffer;
243+
if (!FormatMessageA(Flags, NULL, ErrorCode,
244+
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&Buffer,
245+
0, NULL)) {
246+
return make_error<llvm::StringError>("error " + utostr(ErrorCode),
247+
inconvertibleErrorCode());
248+
}
249+
std::string Message{Buffer};
250+
LocalFree(Buffer);
251+
return make_error<llvm::StringError>(Message, inconvertibleErrorCode());
252+
}
253+
41254
} // namespace
42255

43256
llvm::Expected<std::unique_ptr<DirectoryWatcher>>
44-
clang::DirectoryWatcher::create(
45-
StringRef Path,
46-
std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
47-
bool WaitForInitialSync) {
48-
return llvm::Expected<std::unique_ptr<DirectoryWatcher>>(
49-
llvm::errorCodeToError(std::make_error_code(std::errc::not_supported)));
257+
clang::DirectoryWatcher::create(StringRef Path,
258+
DirectoryWatcherCallback Receiver,
259+
bool WaitForInitialSync) {
260+
if (Path.empty())
261+
llvm::report_fatal_error(
262+
"DirectoryWatcher::create can not accept an empty Path.");
263+
264+
if (!sys::fs::is_directory(Path))
265+
llvm::report_fatal_error(
266+
"DirectoryWatcher::create can not accept a filepath.");
267+
268+
SmallVector<wchar_t, MAX_PATH> WidePath;
269+
if (sys::windows::UTF8ToUTF16(Path, WidePath))
270+
return llvm::make_error<llvm::StringError>(
271+
"unable to convert path to UTF-16", llvm::inconvertibleErrorCode());
272+
273+
DWORD DesiredAccess = FILE_LIST_DIRECTORY;
274+
DWORD ShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
275+
DWORD CreationDisposition = OPEN_EXISTING;
276+
DWORD FlagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
277+
278+
HANDLE DirectoryHandle =
279+
CreateFileW(WidePath.data(), DesiredAccess, ShareMode,
280+
/*lpSecurityAttributes=*/NULL, CreationDisposition,
281+
FlagsAndAttributes, NULL);
282+
if (DirectoryHandle == INVALID_HANDLE_VALUE)
283+
return error(GetLastError());
284+
285+
// NOTE: We use the watcher instance as a RAII object to discard the handles
286+
// for the directory and the IOCP in case of an error. Hence, this is early
287+
// allocated, with the state being written directly to the watcher.
288+
return std::make_unique<DirectoryWatcherWindows>(
289+
DirectoryHandle, WaitForInitialSync, Receiver);
50290
}

clang/unittests/DirectoryWatcher/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
if(APPLE OR CMAKE_SYSTEM_NAME MATCHES "Linux")
1+
if(APPLE OR CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME STREQUAL Windows)
22

33
set(LLVM_LINK_COMPONENTS
44
Support

0 commit comments

Comments
 (0)