Skip to content

Commit ce62c23

Browse files
author
Lewin Bormann
committed
FsWatch: more reliable construction even when facing errors
1 parent 7f8034c commit ce62c23

File tree

4 files changed

+73
-45
lines changed

4 files changed

+73
-45
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pkg_check_modules(FMT REQUIRED fmt)
99
pkg_check_modules(UV REQUIRED libuv)
1010

1111
# Allow using a custom libuv installation.
12-
link_directories(BEFORE /usr/local/lib64)
12+
link_directories(BEFORE /usr/local/lib64 /usr/local/lib)
1313

1414
find_package(Boost 1.80 COMPONENTS log log_setup program_options REQUIRED)
1515
find_package(CURL)

test/fs_test.cc

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "uvco/loop/loop.h"
1111
#include "uvco/promise/multipromise.h"
1212
#include "uvco/promise/promise.h"
13+
#include "uvco/run.h"
1314

1415
#include <algorithm>
1516
#include <cstddef>
@@ -161,7 +162,7 @@ TEST(FsWatchTest, basicFileWatch) {
161162
auto setup = [](const Loop &loop) -> Promise<void> {
162163
auto file = co_await File::open(loop, filename, O_RDWR | O_CREAT);
163164

164-
auto watch = FsWatch::create(loop, filename);
165+
auto watch = co_await FsWatch::create(loop, filename);
165166
MultiPromise<FsWatch::FileEvent> watcher = watch.watch();
166167

167168
co_await file.write("Hello World\n");
@@ -195,7 +196,7 @@ TEST(FsWatchTest, basicDirWatch) {
195196

196197
auto file = co_await File::open(loop, filename, O_RDWR | O_CREAT);
197198

198-
auto watch = FsWatch::create(loop, dirName);
199+
auto watch = co_await FsWatch::create(loop, dirName);
199200
MultiPromise<FsWatch::FileEvent> watcher = watch.watch();
200201

201202
// Check that FsWatch::create is not recursive
@@ -235,7 +236,7 @@ TEST(DISABLED_FsWatchTest, watchRecursive) {
235236
co_await Directory::mkdir(loop, dirName);
236237
co_await Directory::mkdir(loop, subdirName);
237238

238-
auto watch = FsWatch::createRecursive(loop, dirName);
239+
auto watch = co_await FsWatch::createRecursive(loop, dirName);
239240
MultiPromise<FsWatch::FileEvent> watcher = watch.watch();
240241

241242
auto file = co_await File::open(loop, filename, O_RDWR | O_CREAT);
@@ -264,11 +265,12 @@ TEST(FsWatchTest, watchNonExisting) {
264265

265266
auto setup = [](const Loop &loop) -> Promise<void> {
266267
try {
267-
auto watcher = FsWatch::create(loop, filename);
268+
auto watcher = co_await FsWatch::create(loop, filename);
268269
EXPECT_FALSE(true) << "Expected UvcoException";
269270
} catch (const UvcoException &e) {
270271
EXPECT_EQ(e.status, UV_ENOENT);
271272
}
273+
co_await yield();
272274
co_return;
273275
};
274276

@@ -281,7 +283,7 @@ TEST(FsWatchTest, repeatedWatchFails) {
281283
auto setup = [](const Loop &loop) -> Promise<void> {
282284
auto file = co_await File::open(loop, filename, O_RDWR | O_CREAT);
283285

284-
auto watch = FsWatch::create(loop, filename);
286+
auto watch = co_await FsWatch::create(loop, filename);
285287
MultiPromise<FsWatch::FileEvent> watcher = watch.watch();
286288

287289
try {

uvco/fs.cc

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -280,66 +280,80 @@ Promise<void> File::close() {
280280
co_await awaiter;
281281
}
282282

283+
FsWatch::FsWatch() : uv_handle_{std::make_unique<uv_fs_event_t>()} {}
284+
283285
FsWatch::~FsWatch() {
284-
BOOST_ASSERT_MSG(dataIsNull(&uv_handle_),
286+
if (!uv_handle_) {
287+
return;
288+
}
289+
BOOST_ASSERT_MSG(dataIsNull(uv_handle_.get()),
285290
"don't drop an FsWatch while a watch() generator is "
286291
"running (use stopWatch()!)");
287-
uv_fs_event_stop(&uv_handle_);
292+
uv_fs_event_stop(uv_handle_.get());
293+
}
294+
295+
Promise<FsWatch> FsWatch::create(const Loop &loop, std::string_view path) {
296+
return createWithFlag(loop, path, {});
288297
}
289298

290-
FsWatch FsWatch::create(const Loop &loop, std::string_view path) {
291-
return FsWatch{loop, path, {}};
299+
Promise<FsWatch> FsWatch::createRecursive(const Loop &loop,
300+
std::string_view path) {
301+
return createWithFlag(loop, path, UV_FS_EVENT_RECURSIVE);
292302
}
293303

294-
FsWatch FsWatch::createRecursive(const Loop &loop, std::string_view path) {
295-
return FsWatch{loop, path, UV_FS_EVENT_RECURSIVE};
304+
Promise<FsWatch> FsWatch::createWithFlag(const Loop &loop,
305+
std::string_view path,
306+
uv_fs_event_flags flags) {
307+
FsWatch fsWatch;
308+
uv_fs_event_t &uv_handle = *fsWatch.uv_handle_;
309+
const uv_status initStatus = uv_fs_event_init(loop.uvloop(), &uv_handle);
310+
if (initStatus != 0) {
311+
throw UvcoException{
312+
initStatus,
313+
"uv_fs_event_init returned error while initializing FsWatch"};
314+
}
315+
const auto startStatus =
316+
callWithNullTerminated<uv_status>(path, [&](std::string_view safePath) {
317+
return uv_fs_event_start(&uv_handle, onFsWatcherEvent, safePath.data(),
318+
flags);
319+
});
320+
if (startStatus != 0) {
321+
uv_fs_event_stop(&uv_handle);
322+
// This works with the current Loop::~Loop implementation.
323+
co_await closeHandle(&uv_handle);
324+
fsWatch.uv_handle_.reset();
325+
throw UvcoException{
326+
startStatus, "uv_fs_event_start returned error while starting FsWatch"};
327+
}
328+
co_return fsWatch;
296329
}
297330

298331
MultiPromise<FsWatch::FileEvent> FsWatch::watch() {
299-
if (!dataIsNull(&uv_handle_)) {
332+
if (!dataIsNull(uv_handle_.get())) {
300333
throw UvcoException(UV_EBUSY, "FsWatch::watch() is already active!");
301334
}
302335
return watch_();
303336
}
304337

305338
MultiPromise<FsWatch::FileEvent> FsWatch::watch_() {
306339
FsWatchAwaiter_ awaiter;
307-
setData(&uv_handle_, &awaiter);
308-
ZeroAtExit<void> zeroAtExit{&uv_handle_.data};
340+
setData(uv_handle_.get(), &awaiter);
341+
ZeroAtExit<void> zeroAtExit{&uv_handle_->data};
309342

310343
while (co_await awaiter) {
311344
co_yield awaiter.events_.get();
312345
}
313346
}
314347

315348
Promise<void> FsWatch::stopWatch(MultiPromise<FsWatch::FileEvent> watcher) {
316-
auto *awaiter = getData<FsWatchAwaiter_>(&uv_handle_);
349+
auto *awaiter = getData<FsWatchAwaiter_>(uv_handle_.get());
317350
awaiter->stop();
318351
co_await watcher;
319352
}
320353

321-
Promise<void> FsWatch::close() { return closeHandle(&uv_handle_); }
322-
323-
FsWatch::FsWatch(const Loop &loop, std::string_view path,
324-
uv_fs_event_flags flags) {
325-
const uv_status initStatus = uv_fs_event_init(loop.uvloop(), &uv_handle_);
326-
if (initStatus != 0) {
327-
throw UvcoException{
328-
initStatus,
329-
"uv_fs_event_init returned error while initializing FsWatch"};
330-
}
331-
const auto startStatus =
332-
callWithNullTerminated<uv_status>(path, [&](std::string_view safePath) {
333-
return uv_fs_event_start(&uv_handle_, onFsWatcherEvent, safePath.data(),
334-
flags);
335-
});
336-
if (startStatus != 0) {
337-
uv_fs_event_stop(&uv_handle_);
338-
// This works with the current Loop::~Loop implementation.
339-
uv_close((uv_handle_t *)&uv_handle_, nullptr);
340-
throw UvcoException{
341-
startStatus, "uv_fs_event_start returned error while starting FsWatch"};
342-
}
354+
Promise<void> FsWatch::close() {
355+
co_await closeHandle(uv_handle_.get());
356+
uv_handle_.reset();
343357
}
344358

345359
void FsWatch::onFsWatcherEvent(uv_fs_event_t *handle, const char *path,

uvco/fs.h

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#pragma once
44

5+
#include <memory>
56
#include <uv.h>
67
#include <uv/unix.h>
78

@@ -103,17 +104,24 @@ class File {
103104

104105
class FsWatch {
105106
public:
107+
FsWatch(const FsWatch &) = delete;
108+
FsWatch(FsWatch &&) = default;
109+
FsWatch &operator=(const FsWatch &) = delete;
110+
FsWatch &operator=(FsWatch &&) = default;
111+
112+
/// Destructor. If the watch is still active, it is stopped. However, it's
113+
/// still required to call close() before dropping the FsWatch.
114+
~FsWatch();
115+
106116
/// Create aa new FsWatch instance. The path is the file or directory to
107117
/// watch.
108-
static FsWatch create(const Loop &loop, std::string_view path);
118+
static Promise<FsWatch> create(const Loop &loop, std::string_view path);
109119

110120
/// Create a recursive watch. NOTE! This is only supported on macOS and
111121
/// Windows; see
112122
/// https://docs.libuv.org/en/v1.x/fs_event.html#c.uv_fs_event_start.
113-
static FsWatch createRecursive(const Loop &loop, std::string_view path);
114-
115-
/// Destructor. If the watch is still active, it is stopped.
116-
~FsWatch();
123+
static Promise<FsWatch> createRecursive(const Loop &loop,
124+
std::string_view path);
117125

118126
/// An event in an observed file. If status is not 0, an error occurred; this
119127
/// doesn't mean however that no more events occur.
@@ -151,7 +159,11 @@ class FsWatch {
151159
Promise<void> close();
152160

153161
private:
154-
FsWatch(const Loop &loop, std::string_view path, uv_fs_event_flags flags);
162+
static Promise<FsWatch> createWithFlag(const Loop &loop,
163+
std::string_view path,
164+
uv_fs_event_flags flags);
165+
FsWatch();
166+
155167
MultiPromise<FileEvent> watch_();
156168
static void onFsWatcherEvent(uv_fs_event_t *handle, const char *path,
157169
int events, uv_status status);
@@ -175,7 +187,7 @@ class FsWatch {
175187
bool stopped_{};
176188
};
177189

178-
uv_fs_event_t uv_handle_{};
190+
std::unique_ptr<uv_fs_event_t> uv_handle_{};
179191
};
180192

181193
/// @}

0 commit comments

Comments
 (0)