Skip to content

Windows directory watcher flaky reporting of link type for new link to directory #61797

@davidmorgan

Description

@davidmorgan

@mraleph another fun one around file watching, any chance you could take a quick look please?

Repro:

import 'dart:async';
import 'dart:io';

void main() async {
  final workspace = Directory.systemTemp.createTempSync();
  final sep = Platform.pathSeparator;
  final targetDir = '${workspace.path}${sep}targets/targetdir';
  Directory(targetDir).createSync(recursive: true);
  final linksDir = '${workspace.path}${sep}links';
  Directory(linksDir).createSync();
  Directory(linksDir).watch().listen((e) {
    print('${e.isDirectory ? 'd' : 'f'} ${e.path}');
  });

  String link(int i) => '${workspace.path}${sep}links${sep}link$i';

  for (var i = 0; i != 25; ++i) {
    await Future<void>.delayed(const Duration(milliseconds: 10));
    unawaited(Link(link(i)).create(targetDir));
  }

  await Future<void>.delayed(const Duration(seconds: 1));
  exit(0);
}

It watches a directory, then creates 25 symlinks to directories in that directory.

The watcher prints each create was reported as a file f or directory d.

On Linux and MacOS all the events are reported as file creates:

f /tmp/QRXSOS/links/link0
f /tmp/QRXSOS/links/link1
f /tmp/QRXSOS/links/link2
f /tmp/QRXSOS/links/link3
f /tmp/QRXSOS/links/link4
...

But on Windows the type randomly flips between the two:

f C:\Users\davidmorgan\AppData\Local\Temp\eb7096fb\links\link0
f C:\Users\davidmorgan\AppData\Local\Temp\eb7096fb\links\link1
d C:\Users\davidmorgan\AppData\Local\Temp\eb7096fb\links\link2
d C:\Users\davidmorgan\AppData\Local\Temp\eb7096fb\links\link3
f C:\Users\davidmorgan\AppData\Local\Temp\eb7096fb\links\link4
d C:\Users\davidmorgan\AppData\Local\Temp\eb7096fb\links\link5
...

So it looks like there is some race between what the OS is doing and what the Dart VM is doing that causes it be reported one way or the other.

Using createSync when creating the links seems to exactly avoid the race, i.e. the Dart VM is busy creating the link so it can't observe the half-created link.

// not this: unawaited(Link(link(i)).create(targetDir));
// but this instead.
Link(link(i)).createSync(targetDir)

but while that "helps" in tests it doesn't help for real cases :) where the link is created by someone else and there can be a race.

If I make the watcher do a FileSystemEntity.typeSync check when the event says it's a new directory

    if (isDirectory &&
        FileSystemEntity.typeSync(e.path, followLinks: false) !=
            FileSystemEntityType.directory) {
      isDirectory = false;
    }

then that helps but not 100%, so "typeSync" apparently also returns type directory for some small period of time during link creation.

So I guess the question is, is there anything the VM can do to make this consistent; if not, what's the workaround, and I guess another caveat in the docs is needed to explain this rather awkward case :)

Thanks :)

Metadata

Metadata

Assignees

Labels

area-vmUse area-vm for VM related issues, including code coverage, and the AOT and JIT backends.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions