blob: f86b6180d9af50d6ce8e0e3b2006cdce33ccd63b [file] [log] [blame] [edit]
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Make sure that file system watcher works when Mac OS X restricts pipe buffer
// size to 512 bytes (see https://github.com/dart-lang/sdk/issues/61551).
import 'dart:async';
import 'dart:io';
import 'dart:ffi';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'package:path/path.dart' as p;
void main() async {
if (!Platform.isMacOS) {
return;
}
exhaustMaxPipeKVA();
var tempDir = await Directory.systemTemp.createTemp('fsevents_test_');
try {
var watcher = tempDir.watch();
var eventsReceived = 0;
var subscription = watcher.listen((event) {
eventsReceived++;
});
// Wait for the watcher to become active.
await Future.delayed(Duration(milliseconds: 500));
// Make some changes in the directory.
final testFile = File(p.join(tempDir.path, 'test_file.txt'));
await testFile.writeAsString('test content');
await testFile.writeAsString('modified content', mode: FileMode.append);
await testFile.delete();
// Wait a bit for events to arrive.
await Future.delayed(Duration(seconds: 2));
// Cancel the watcher.
await subscription.cancel();
// We should have received at least some events.
Expect.isTrue(eventsReceived > 0);
} finally {
await tempDir.delete(recursive: true);
}
}
// Create pipes and force their buffers to grow to 64KB until we reach
// kern.ipc.maxpipekva. This should not take more than 256 pipes because
// the limit is 16 MB.
void exhaustMaxPipeKVA() {
final fds = calloc<Int>(2);
const writeSize = 64 * 1024;
final buf = calloc<Int8>(writeSize);
for (int i = 0; i < 256; i++) {
final pipeRes = pipe(fds);
// We do not expect to run out of file descriptors before we run out of space
// in kernel for pipebuffers.
Expect.equals(0, pipeRes, 'Failed to create a pipe');
var writeFd = (fds + 1).value;
const F_GETFL = 3;
const F_SETFL = 4;
const O_NONBLOCK = 0x00000004;
final flags = fcntl0(writeFd, F_GETFL);
Expect.isTrue(flags != -1, 'Failed to call fcntl($writeFd, F_GETFL)');
final setFlagsRes = fcntl1(writeFd, F_SETFL, flags | O_NONBLOCK);
Expect.isTrue(setFlagsRes != -1, 'Failed to call fcntl($writeFd, F_SETFL)');
if (write(writeFd, buf.cast(), writeSize) != writeSize) {
break;
}
}
}
@Native<Int Function(Pointer<Int>)>()
external int pipe(Pointer<Int> fds);
@Native<Int Function(Pointer<Int>)>()
external int fnctl(Pointer<Int> fds);
@Native<Int Function(Int, Int, VarArgs<()>)>(symbol: 'fcntl')
external int fcntl0(int fd, int cmd);
@Native<Int Function(Int, Int, VarArgs<(Int,)>)>(symbol: 'fcntl')
external int fcntl1(int fd, int cmd, int val);
@Native<Int Function(Int, Pointer<Void> buf, Size)>()
external int write(int fd, Pointer<Void> buf, int size);