fix(fsevents): handle just the final state for coalesced events
This commit is contained in:
parent
ea72e0654d
commit
7d7241170f
2 changed files with 33 additions and 9 deletions
|
|
@ -229,21 +229,36 @@ fn callback(
|
||||||
// an error back to the caller. Stopping the stream from inside the
|
// an error back to the caller. Stopping the stream from inside the
|
||||||
// callback would require a separate signal channel and is not worth
|
// callback would require a separate signal channel and is not worth
|
||||||
// the complexity; the stream will keep delivering future events.
|
// the complexity; the stream will keep delivering future events.
|
||||||
if (flags & kFSEventStreamEventFlagItemCreated != 0) {
|
// FSEvents coalesces multiple operations into a single callback with
|
||||||
ctx.handler.change(path, .created, ot) catch {};
|
// multiple flags set. Processing all flags independently produces
|
||||||
}
|
// spurious duplicate events (e.g. ItemRemoved|ItemRenamed -> two
|
||||||
|
// deletes; ItemCreated|ItemRemoved -> spurious create before delete).
|
||||||
|
//
|
||||||
|
// Priority chain: Removed > Renamed > Modified > Created.
|
||||||
|
// Modified beats Created because FSEvents sets ItemCreated on writes to
|
||||||
|
// existing files when O_CREAT is used (e.g. `echo > file`), producing
|
||||||
|
// spurious create events. When both are set, the file already exists,
|
||||||
|
// so Modified is the accurate description.
|
||||||
|
//
|
||||||
|
// Exception: ItemRenamed|ItemModified on an existing path emits both
|
||||||
|
// created and modified, because a rename-in followed by a write can be
|
||||||
|
// coalesced by FSEvents into a single callback.
|
||||||
if (flags & kFSEventStreamEventFlagItemRemoved != 0) {
|
if (flags & kFSEventStreamEventFlagItemRemoved != 0) {
|
||||||
ctx.handler.change(path, .deleted, ot) catch {};
|
ctx.handler.change(path, .deleted, ot) catch {};
|
||||||
}
|
} else if (flags & kFSEventStreamEventFlagItemRenamed != 0) {
|
||||||
if (flags & kFSEventStreamEventFlagItemRenamed != 0) {
|
|
||||||
// FSEvents fires ItemRenamed for both sides of a rename unpaired.
|
// FSEvents fires ItemRenamed for both sides of a rename unpaired.
|
||||||
// Normalize to created/deleted based on whether the path still exists,
|
// Normalize to created/deleted based on whether the path still exists,
|
||||||
// so move-in appears as created and move-out as deleted on all platforms.
|
// so move-in appears as created and move-out as deleted on all platforms.
|
||||||
const exists = if (std.fs.accessAbsolute(path, .{})) |_| true else |_| false;
|
const exists = if (std.fs.accessAbsolute(path, .{})) |_| true else |_| false;
|
||||||
ctx.handler.change(path, if (exists) .created else .deleted, ot) catch {};
|
ctx.handler.change(path, if (exists) .created else .deleted, ot) catch {};
|
||||||
}
|
// If a write was coalesced with a move-in, also emit the modify.
|
||||||
if (flags & kFSEventStreamEventFlagItemModified != 0) {
|
if (exists and flags & kFSEventStreamEventFlagItemModified != 0) {
|
||||||
|
ctx.handler.change(path, .modified, ot) catch {};
|
||||||
|
}
|
||||||
|
} else if (flags & kFSEventStreamEventFlagItemModified != 0) {
|
||||||
ctx.handler.change(path, .modified, ot) catch {};
|
ctx.handler.change(path, .modified, ot) catch {};
|
||||||
|
} else if (flags & kFSEventStreamEventFlagItemCreated != 0) {
|
||||||
|
ctx.handler.change(path, .created, ot) catch {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,11 @@ fn testModifyFile(comptime Watcher: type, allocator: std.mem.Allocator) !void {
|
||||||
var watcher = try Watcher.init(allocator, &th.handler);
|
var watcher = try Watcher.init(allocator, &th.handler);
|
||||||
defer watcher.deinit();
|
defer watcher.deinit();
|
||||||
try watcher.watch(tmp);
|
try watcher.watch(tmp);
|
||||||
|
// Drain before writing: FSEvents may deliver a coalesced create+modify if the
|
||||||
|
// file was created just before the stream started. A drain here separates any
|
||||||
|
// stale creation event from the upcoming write, so the write arrives in its
|
||||||
|
// own callback with only ItemModified set.
|
||||||
|
try drainEvents(Watcher, &watcher);
|
||||||
|
|
||||||
{
|
{
|
||||||
const f = try std.fs.openFileAbsolute(file_path, .{ .mode = .write_only });
|
const f = try std.fs.openFileAbsolute(file_path, .{ .mode = .write_only });
|
||||||
|
|
@ -794,7 +799,9 @@ test "creating a file emits a 'created' event" {
|
||||||
|
|
||||||
test "writing to a file emits a 'modified' event" {
|
test "writing to a file emits a 'modified' event" {
|
||||||
inline for (comptime std.enums.values(nw.Variant)) |variant| {
|
inline for (comptime std.enums.values(nw.Variant)) |variant| {
|
||||||
try testModifyFile(nw.Create(variant), std.testing.allocator);
|
testModifyFile(nw.Create(variant), std.testing.allocator) catch |e| {
|
||||||
|
if (e != error.SkipZigTest) return e;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -860,7 +867,9 @@ test "rename: old-name event precedes new-name event" {
|
||||||
|
|
||||||
test "rename-then-modify: rename event precedes the subsequent modify event" {
|
test "rename-then-modify: rename event precedes the subsequent modify event" {
|
||||||
inline for (comptime std.enums.values(nw.Variant)) |variant| {
|
inline for (comptime std.enums.values(nw.Variant)) |variant| {
|
||||||
try testRenameThenModify(nw.Create(variant), std.testing.allocator);
|
testRenameThenModify(nw.Create(variant), std.testing.allocator) catch |e| {
|
||||||
|
if (e != error.SkipZigTest) return e;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue