Version 1.5.0-dev.4.1

svn merge -c 36984 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
svn merge -c 36988 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
svn merge -c 36990 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
svn merge -c 36991 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
svn merge -c 36995 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
svn merge -c 36999 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
svn merge -c 37003 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
svn merge -c 37006 https://dart.googlecode.com/svn/branches/bleeding_edge trunk

git-svn-id: http://dart.googlecode.com/svn/trunk@37028 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkg/analysis_server/test/physical_resource_provider_test.dart b/pkg/analysis_server/test/physical_resource_provider_test.dart
index f2af810..e3dfdc7 100644
--- a/pkg/analysis_server/test/physical_resource_provider_test.dart
+++ b/pkg/analysis_server/test/physical_resource_provider_test.dart
@@ -32,24 +32,23 @@
       group('Watch', () {
 
         Future delayed(computation()) {
-          // On Windows, watching the filesystem is accomplished by polling once
-          // per second.  So wait 2 seconds to give time for polling to reliably
-          // occur.
-          return new Future.delayed(new Duration(seconds: 2), computation);
+          return new Future.delayed(
+              new Duration(seconds: 1), computation);
         }
 
         watchingFolder(String path, test(List<WatchEvent> changesReceived)) {
           // Delay before we start watching the folder.  This is necessary
-          // because on MacOS, file modifications that occur just before we start
-          // watching are sometimes misclassified as happening just after we
-          // start watching.
+          // because on MacOS, file modifications that occur just before we
+          // start watching are sometimes misclassified as happening just after
+          // we start watching.
           return delayed(() {
             Folder folder = PhysicalResourceProvider.INSTANCE.getResource(path);
             var changesReceived = <WatchEvent>[];
             var subscription = folder.changes.listen(changesReceived.add);
-            // Delay running the rest of the test to allow folder.changes to take
-            // a snapshot of the current directory state.  Otherwise it won't be
-            // able to reliably distinguish new files from modified ones.
+            // Delay running the rest of the test to allow folder.changes to
+            // take a snapshot of the current directory state.  Otherwise it
+            // won't be able to reliably distinguish new files from modified
+            // ones.
             return delayed(() => test(changesReceived)).whenComplete(() {
               subscription.cancel();
             });
diff --git a/pkg/pkg.status b/pkg/pkg.status
index f7e1cff..6a5b814 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -368,6 +368,9 @@
 [ $runtime == vm && ($system == windows || $system == linux) ]
 watcher/test/*/mac_os_test: Skip
 
+[ $runtime == vm && ($system == macos || $system == linux) ]
+watcher/test/*/windows_test: Skip
+
 [ $runtime == safari || $runtime == safarimobilesim || $runtime == chrome || $runtime == ff || $ie ]
 # Various issues due to limited browser testing in Angular.
 third_party/angular_tests/*: Skip
diff --git a/pkg/watcher/lib/src/directory_watcher.dart b/pkg/watcher/lib/src/directory_watcher.dart
index 78b1174..605eaea 100644
--- a/pkg/watcher/lib/src/directory_watcher.dart
+++ b/pkg/watcher/lib/src/directory_watcher.dart
@@ -10,6 +10,7 @@
 import 'watch_event.dart';
 import 'directory_watcher/linux.dart';
 import 'directory_watcher/mac_os.dart';
+import 'directory_watcher/windows.dart';
 import 'directory_watcher/polling.dart';
 
 /// Watches the contents of a directory and emits [WatchEvent]s when something
@@ -54,8 +55,11 @@
   /// and higher CPU usage. Defaults to one second. Ignored for non-polling
   /// watchers.
   factory DirectoryWatcher(String directory, {Duration pollingDelay}) {
-    if (Platform.isLinux) return new LinuxDirectoryWatcher(directory);
-    if (Platform.isMacOS) return new MacOSDirectoryWatcher(directory);
+    if (FileSystemEntity.isWatchSupported) {
+      if (Platform.isLinux) return new LinuxDirectoryWatcher(directory);
+      if (Platform.isMacOS) return new MacOSDirectoryWatcher(directory);
+      if (Platform.isWindows) return new WindowsDirectoryWatcher(directory);
+    }
     return new PollingDirectoryWatcher(directory, pollingDelay: pollingDelay);
   }
 }
diff --git a/pkg/watcher/lib/src/directory_watcher/windows.dart b/pkg/watcher/lib/src/directory_watcher/windows.dart
new file mode 100644
index 0000000..ddcf897
--- /dev/null
+++ b/pkg/watcher/lib/src/directory_watcher/windows.dart
@@ -0,0 +1,419 @@
+// Copyright (c) 2014, 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.

+// TODO(rnystrom): Merge with mac_os version.

+

+library watcher.directory_watcher.windows;

+

+import 'dart:async';

+import 'dart:collection';

+import 'dart:io';

+

+import 'package:path/path.dart' as p;

+import 'package:stack_trace/stack_trace.dart';

+

+import '../constructable_file_system_event.dart';

+import '../path_set.dart';

+import '../utils.dart';

+import '../watch_event.dart';

+import 'resubscribable.dart';

+

+class WindowsDirectoryWatcher extends ResubscribableDirectoryWatcher {

+  WindowsDirectoryWatcher(String directory)

+      : super(directory, () => new _WindowsDirectoryWatcher(directory));

+}

+

+class _EventBatcher {

+  static const Duration _BATCH_DELAY = const Duration(milliseconds: 100);

+  final List<FileSystemEvent> events = [];

+  Timer timer;

+

+  void addEvent(FileSystemEvent event) {

+    events.add(event);

+  }

+

+  void startTimer(void callback()) {

+    if (timer != null) {

+      timer.cancel();

+    }

+    timer = new Timer(_BATCH_DELAY, callback);

+  }

+

+  void cancelTimer() {

+    timer.cancel();

+  }

+}

+

+class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher {

+  final String directory;

+

+  Stream<WatchEvent> get events => _eventsController.stream;

+  final _eventsController = new StreamController<WatchEvent>.broadcast();

+

+  bool get isReady => _readyCompleter.isCompleted;

+

+  Future get ready => _readyCompleter.future;

+  final _readyCompleter = new Completer();

+

+  final Map<String, _EventBatcher> _eventBatchers =

+      new HashMap<String, _EventBatcher>();

+

+  /// The set of files that are known to exist recursively within the watched

+  /// directory.

+  ///

+  /// The state of files on the filesystem is compared against this to determine

+  /// the real change that occurred. This is also used to emit REMOVE events

+  /// when subdirectories are moved out of the watched directory.

+  final PathSet _files;

+

+  /// The subscription to the stream returned by [Directory.watch].

+  StreamSubscription<FileSystemEvent> _watchSubscription;

+

+  /// The subscription to the stream returned by [Directory.watch] of the

+  /// parent directory to [directory]. This is needed to detect changes to

+  /// [directory], as they are not included on Windows.

+  StreamSubscription<FileSystemEvent> _parentWatchSubscription;

+

+  /// The subscription to the [Directory.list] call for the initial listing of

+  /// the directory to determine its initial state.

+  StreamSubscription<FileSystemEntity> _initialListSubscription;

+

+  /// The subscriptions to the [Directory.list] call for listing the contents of

+  /// subdirectories that was moved into the watched directory.

+  final Set<StreamSubscription<FileSystemEntity>> _listSubscriptions

+      = new HashSet<StreamSubscription<FileSystemEntity>>();

+

+  _WindowsDirectoryWatcher(String directory)

+      : directory = directory, _files = new PathSet(directory) {

+    _startWatch();

+    _startParentWatcher();

+

+    // Before we're ready to emit events, wait for [_listDir] to complete.

+    _listDir().then(_readyCompleter.complete);

+  }

+

+  void close() {

+    if (_watchSubscription != null) _watchSubscription.cancel();

+    if (_parentWatchSubscription != null) _parentWatchSubscription.cancel();

+    if (_initialListSubscription != null) _initialListSubscription.cancel();

+    for (var sub in _listSubscriptions) {

+      sub.cancel();

+    }

+    _listSubscriptions.clear();

+    for (var batcher in _eventBatchers.values) {

+      batcher.cancelTimer();

+    }

+    _eventBatchers.clear();

+    _watchSubscription = null;

+    _parentWatchSubscription = null;

+    _initialListSubscription = null;

+    _eventsController.close();

+  }

+

+  /// On Windows, if [directory] is deleted, we will not receive any event.

+  /// Instead, we add a watcher on the parent folder (if any), that can notify

+  /// us about [directory].

+  /// This also includes events such as moves.

+  void _startParentWatcher() {

+    var absoluteDir = p.absolute(directory);

+    var parent = p.dirname(absoluteDir);

+    // Check if we [directory] is already the root directory.

+    if (FileSystemEntity.identicalSync(parent, directory)) return;

+    var parentStream = Chain.track(

+        new Directory(parent).watch(recursive: false));

+    _parentWatchSubscription = parentStream.listen((event) {

+      // Only look at events for 'directory'.

+      if (p.basename(event.path) != p.basename(absoluteDir)) return;

+      // Test if the directory is removed. FileSystemEntity.typeSync will

+      // return NOT_FOUND if it's unable to decide upon the type, including

+      // access denied issues, which may happen when the directory is deleted.

+      // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean

+      // the directory is now gone.

+      if (event is FileSystemMoveEvent ||

+          event is FileSystemDeleteEvent ||

+          (FileSystemEntity.typeSync(directory) ==

+           FileSystemEntityType.NOT_FOUND)) {

+        for (var path in _files.toSet()) {

+          _emitEvent(ChangeType.REMOVE, path);

+        }

+        _files.clear();

+        close();

+      }

+    }, onError: (error) {

+      // Ignore errors, simply close the stream.

+      _parentWatchSubscription.cancel();

+      _parentWatchSubscription = null;

+    });

+  }

+

+  void _onEvent(FileSystemEvent event) {

+    // If we get a event before we're ready to begin emitting events,

+    // ignore those events and re-list the directory.

+    if (!isReady) {

+      _listDir().then((_) {

+       _readyCompleter.complete();

+      });

+      return;

+    }

+

+    _EventBatcher batcher = _eventBatchers.putIfAbsent(

+        event.path, () => new _EventBatcher());

+    batcher.addEvent(event);

+    batcher.startTimer(() {

+      _eventBatchers.remove(event.path);

+      _onBatch(batcher.events);

+    });

+  }

+

+  /// The callback that's run when [Directory.watch] emits a batch of events.

+  void _onBatch(List<FileSystemEvent> batch) {

+    _sortEvents(batch).forEach((path, events) {

+      var relativePath = p.relative(path, from: directory);

+

+      var canonicalEvent = _canonicalEvent(events);

+      events = canonicalEvent == null ?

+          _eventsBasedOnFileSystem(path) : [canonicalEvent];

+

+      for (var event in events) {

+        if (event is FileSystemCreateEvent) {

+          if (!event.isDirectory) {

+            if (_files.contains(path)) continue;

+

+            _emitEvent(ChangeType.ADD, path);

+            _files.add(path);

+            continue;

+          }

+

+          if (_files.containsDir(path)) continue;

+

+          var stream = Chain.track(new Directory(path).list(recursive: true));

+          var sub;

+          sub = stream.listen((entity) {

+            if (entity is Directory) return;

+            if (_files.contains(path)) return;

+

+            _emitEvent(ChangeType.ADD, entity.path);

+            _files.add(entity.path);

+          }, onDone: () {

+            _listSubscriptions.remove(sub);

+          }, onError: (e, stackTrace) {

+            _listSubscriptions.remove(sub);

+            _emitError(e, stackTrace);

+          }, cancelOnError: true);

+          _listSubscriptions.add(sub);

+        } else if (event is FileSystemModifyEvent) {

+          if (!event.isDirectory) {

+            _emitEvent(ChangeType.MODIFY, path);

+          }

+        } else {

+          assert(event is FileSystemDeleteEvent);

+          for (var removedPath in _files.remove(path)) {

+            _emitEvent(ChangeType.REMOVE, removedPath);

+          }

+        }

+      }

+    });

+  }

+

+  /// Sort all the events in a batch into sets based on their path.

+  ///

+  /// A single input event may result in multiple events in the returned map;

+  /// for example, a MOVE event becomes a DELETE event for the source and a

+  /// CREATE event for the destination.

+  ///

+  /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it

+  /// contain any events relating to [directory].

+  Map<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) {

+    var eventsForPaths = {};

+

+    // Events within directories that already have events are superfluous; the

+    // directory's full contents will be examined anyway, so we ignore such

+    // events. Emitting them could cause useless or out-of-order events.

+    var directories = unionAll(batch.map((event) {

+      if (!event.isDirectory) return new Set();

+      if (event is! FileSystemMoveEvent) return new Set.from([event.path]);

+      return new Set.from([event.path, event.destination]);

+    }));

+

+    isInModifiedDirectory(path) =>

+        directories.any((dir) => path != dir && path.startsWith(dir));

+

+    addEvent(path, event) {

+      if (isInModifiedDirectory(path)) return;

+      var set = eventsForPaths.putIfAbsent(path, () => new Set());

+      set.add(event);

+    }

+

+    for (var event in batch) {

+      if (event is FileSystemMoveEvent) {

+        FileSystemMoveEvent moveEvent = event;

+        addEvent(moveEvent.destination, event);

+      }

+      addEvent(event.path, event);

+    }

+

+    return eventsForPaths;

+  }

+

+  /// Returns the canonical event from a batch of events on the same path, if

+  /// one exists.

+  ///

+  /// If [batch] doesn't contain any contradictory events (e.g. DELETE and

+  /// CREATE, or events with different values for [isDirectory]), this returns a

+  /// single event that describes what happened to the path in question.

+  ///

+  /// If [batch] does contain contradictory events, this returns `null` to

+  /// indicate that the state of the path on the filesystem should be checked to

+  /// determine what occurred.

+  FileSystemEvent _canonicalEvent(Set<FileSystemEvent> batch) {

+    // An empty batch indicates that we've learned earlier that the batch is

+    // contradictory (e.g. because of a move).

+    if (batch.isEmpty) return null;

+

+    var type = batch.first.type;

+    var isDir = batch.first.isDirectory;

+    var hadModifyEvent = false;

+

+    for (var event in batch.skip(1)) {

+      // If one event reports that the file is a directory and another event

+      // doesn't, that's a contradiction.

+      if (isDir != event.isDirectory) return null;

+

+      // Modify events don't contradict either CREATE or REMOVE events. We can

+      // safely assume the file was modified after a CREATE or before the

+      // REMOVE; otherwise there will also be a REMOVE or CREATE event

+      // (respectively) that will be contradictory.

+      if (event is FileSystemModifyEvent) {

+        hadModifyEvent = true;

+        continue;

+      }

+      assert(event is FileSystemCreateEvent ||

+             event is FileSystemDeleteEvent ||

+             event is FileSystemMoveEvent);

+

+      // If we previously thought this was a MODIFY, we now consider it to be a

+      // CREATE or REMOVE event. This is safe for the same reason as above.

+      if (type == FileSystemEvent.MODIFY) {

+        type = event.type;

+        continue;

+      }

+

+      // A CREATE event contradicts a REMOVE event and vice versa.

+      assert(type == FileSystemEvent.CREATE ||

+             type == FileSystemEvent.DELETE ||

+             type == FileSystemEvent.MOVE);

+      if (type != event.type) return null;

+    }

+

+    switch (type) {

+      case FileSystemEvent.CREATE:

+        return new ConstructableFileSystemCreateEvent(batch.first.path, isDir);

+      case FileSystemEvent.DELETE:

+        return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir);

+      case FileSystemEvent.MODIFY:

+        return new ConstructableFileSystemModifyEvent(

+            batch.first.path, isDir, false);

+      case FileSystemEvent.MOVE:

+        return null;

+      default: assert(false);

+    }

+  }

+

+  /// Returns one or more events that describe the change between the last known

+  /// state of [path] and its current state on the filesystem.

+  ///

+  /// This returns a list whose order should be reflected in the events emitted

+  /// to the user, unlike the batched events from [Directory.watch]. The

+  /// returned list may be empty, indicating that no changes occurred to [path]

+  /// (probably indicating that it was created and then immediately deleted).

+  List<FileSystemEvent> _eventsBasedOnFileSystem(String path) {

+    var fileExisted = _files.contains(path);

+    var dirExisted = _files.containsDir(path);

+    var fileExists = new File(path).existsSync();

+    var dirExists = new Directory(path).existsSync();

+

+    var events = [];

+    if (fileExisted) {

+      if (fileExists) {

+        events.add(new ConstructableFileSystemModifyEvent(path, false, false));

+      } else {

+        events.add(new ConstructableFileSystemDeleteEvent(path, false));

+      }

+    } else if (dirExisted) {

+      if (dirExists) {

+        // If we got contradictory events for a directory that used to exist and

+        // still exists, we need to rescan the whole thing in case it was

+        // replaced with a different directory.

+        events.add(new ConstructableFileSystemDeleteEvent(path, true));

+        events.add(new ConstructableFileSystemCreateEvent(path, true));

+      } else {

+        events.add(new ConstructableFileSystemDeleteEvent(path, true));

+      }

+    }

+

+    if (!fileExisted && fileExists) {

+      events.add(new ConstructableFileSystemCreateEvent(path, false));

+    } else if (!dirExisted && dirExists) {

+      events.add(new ConstructableFileSystemCreateEvent(path, true));

+    }

+

+    return events;

+  }

+

+  /// The callback that's run when the [Directory.watch] stream is closed.

+  /// Note that this is unlikely to happen on Windows, unless the system itself

+  /// closes the handle.

+  void _onDone() {

+    _watchSubscription = null;

+

+    // Emit remove-events for any remaining files.

+    for (var file in _files.toSet()) {

+      _emitEvent(ChangeType.REMOVE, file);

+    }

+    _files.clear();

+    close();

+  }

+

+  /// Start or restart the underlying [Directory.watch] stream.

+  void _startWatch() {

+    // Batch the events changes together so that we can dedup events.

+    var innerStream =

+        Chain.track(new Directory(directory).watch(recursive: true));

+    _watchSubscription = innerStream.listen(_onEvent,

+        onError: _eventsController.addError,

+        onDone: _onDone);

+  }

+

+  /// Starts or restarts listing the watched directory to get an initial picture

+  /// of its state.

+  Future _listDir() {

+    assert(!isReady);

+    if (_initialListSubscription != null) _initialListSubscription.cancel();

+

+    _files.clear();

+    var completer = new Completer();

+    var stream = Chain.track(new Directory(directory).list(recursive: true));

+    void handleEntity(entity) {

+      if (entity is! Directory) _files.add(entity.path);

+    }

+    _initialListSubscription = stream.listen(

+        handleEntity,

+        onError: _emitError,

+        onDone: completer.complete,

+        cancelOnError: true);

+    return completer.future;

+  }

+

+  /// Emit an event with the given [type] and [path].

+  void _emitEvent(ChangeType type, String path) {

+    if (!isReady) return;

+

+    _eventsController.add(new WatchEvent(type, path));

+  }

+

+  /// Emit an error, then close the watcher.

+  void _emitError(error, StackTrace stackTrace) {

+    _eventsController.addError(error, stackTrace);

+    close();

+  }

+}

diff --git a/pkg/watcher/test/directory_watcher/windows_test.dart b/pkg/watcher/test/directory_watcher/windows_test.dart
new file mode 100644
index 0000000..3e9a854
--- /dev/null
+++ b/pkg/watcher/test/directory_watcher/windows_test.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2014, 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.

+

+import 'package:path/path.dart' as p;

+import 'package:scheduled_test/scheduled_test.dart';

+import 'package:watcher/src/directory_watcher/windows.dart';

+import 'package:watcher/watcher.dart';

+

+import 'shared.dart';

+import '../utils.dart';

+

+main() {

+  initConfig();

+

+  watcherFactory = (dir) => new WindowsDirectoryWatcher(dir);

+

+  setUp(createSandbox);

+

+  sharedTests();

+

+  test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () {

+    expect(new DirectoryWatcher('.'),

+        new isInstanceOf<WindowsDirectoryWatcher>());

+  });

+

+  test('when the watched directory is moved, removes all files', () {

+    writeFile("dir/a.txt");

+    writeFile("dir/b.txt");

+

+    startWatcher(dir: "dir");

+

+    renameDir("dir", "moved_dir");

+    createDir("dir");

+    inAnyOrder([

+      isRemoveEvent("dir/a.txt"),

+      isRemoveEvent("dir/b.txt")

+    ]);

+  });

+}

+

diff --git a/pkg/watcher/test/utils.dart b/pkg/watcher/test/utils.dart
index b520bbd..8b660e8 100644
--- a/pkg/watcher/test/utils.dart
+++ b/pkg/watcher/test/utils.dart
@@ -71,6 +71,11 @@
   // Delete the sandbox when done.
   currentSchedule.onComplete.schedule(() {
     if (_sandboxDir != null) {
+      // TODO(rnystrom): Issue 19155. The watcher should already be closed when
+      // we clean up the sandbox.
+      if (_watcherEvents != null) {
+        _watcherEvents.close();
+      }
       new Directory(_sandboxDir).deleteSync(recursive: true);
       _sandboxDir = null;
     }
diff --git a/runtime/bin/eventhandler_win.cc b/runtime/bin/eventhandler_win.cc
index 3b12d94..c16f1a5 100644
--- a/runtime/bin/eventhandler_win.cc
+++ b/runtime/bin/eventhandler_win.cc
@@ -160,7 +160,7 @@
 
 
 bool Handle::CreateCompletionPort(HANDLE completion_port) {
-  completion_port_ = CreateIoCompletionPort(handle_,
+  completion_port_ = CreateIoCompletionPort(handle(),
                                             completion_port,
                                             reinterpret_cast<ULONG_PTR>(this),
                                             0);
@@ -176,17 +176,19 @@
   if (!IsClosing()) {
     // Close the socket and set the closing state. This close method can be
     // called again if this socket has pending IO operations in flight.
-    ASSERT(handle_ != INVALID_HANDLE_VALUE);
     MarkClosing();
     // Perform handle type specific closing.
     DoClose();
   }
+  ASSERT(IsHandleClosed());
 }
 
 
 void Handle::DoClose() {
-  CloseHandle(handle_);
-  handle_ = INVALID_HANDLE_VALUE;
+  if (!IsHandleClosed()) {
+    CloseHandle(handle_);
+    handle_ = INVALID_HANDLE_VALUE;
+  }
 }
 
 
@@ -396,7 +398,7 @@
   ScopedLock lock(this);
   // It may have been started before, as we start the directory-handler when
   // we create it.
-  if (pending_read_ != NULL) return true;
+  if (pending_read_ != NULL || data_ready_ != NULL) return true;
   OverlappedBuffer* buffer = OverlappedBuffer::AllocateReadBuffer(kBufferSize);
   ASSERT(completion_port_ != INVALID_HANDLE_VALUE);
   BOOL ok = ReadDirectoryChangesW(handle_,
@@ -417,6 +419,19 @@
 }
 
 
+void DirectoryWatchHandle::Stop() {
+  ScopedLock lock(this);
+  // Stop the outstanding read, so we can close the handle.
+
+  if (pending_read_ != NULL) {
+    CancelIoEx(handle(), pending_read_->GetCleanOverlapped());
+    // Don't dispose of the buffer, as it will still complete (with length 0).
+  }
+
+  DoClose();
+}
+
+
 void SocketHandle::HandleIssueError() {
   int error = WSAGetLastError();
   if (error == WSAECONNRESET) {
@@ -793,6 +808,7 @@
   // Always do a shutdown before initiating a disconnect.
   shutdown(socket(), SD_BOTH);
   IssueDisconnect();
+  handle_ = INVALID_HANDLE_VALUE;
 }
 
 
diff --git a/runtime/bin/eventhandler_win.h b/runtime/bin/eventhandler_win.h
index e1f96f7..4bac8f4 100644
--- a/runtime/bin/eventhandler_win.h
+++ b/runtime/bin/eventhandler_win.h
@@ -93,7 +93,7 @@
     wbuf_.buf = GetBufferStart();
     wbuf_.len = GetBufferSize();
     return &wbuf_;
-  };
+  }
 
   void set_data_length(int data_length) { data_length_ = data_length; }
 
@@ -234,6 +234,8 @@
   virtual void DoClose();
   virtual bool IsClosed() = 0;
 
+  bool IsHandleClosed() const { return handle_ == INVALID_HANDLE_VALUE; }
+
   void SetPortAndMask(Dart_Port port, intptr_t mask) {
     port_ = port;
     mask_ = mask;
@@ -349,6 +351,8 @@
 
   virtual bool IssueRead();
 
+  void Stop();
+
  private:
   int events_;
   bool recursive_;
@@ -357,14 +361,20 @@
 
 class SocketHandle : public Handle {
  public:
-  SOCKET socket() { return reinterpret_cast<SOCKET>(handle_); }
+  SOCKET socket() const { return socket_; }
 
  protected:
-  explicit SocketHandle(SOCKET s) : Handle(reinterpret_cast<HANDLE>(s)) {}
+  explicit SocketHandle(SOCKET s)
+      : Handle(reinterpret_cast<HANDLE>(s)),
+        socket_(s) {}
   SocketHandle(SOCKET s, Dart_Port port)
-      : Handle(reinterpret_cast<HANDLE>(s), port) {}
+      : Handle(reinterpret_cast<HANDLE>(s), port),
+        socket_(s) {}
 
   virtual void HandleIssueError();
+
+ private:
+  const SOCKET socket_;
 };
 
 
@@ -382,7 +392,7 @@
     ASSERT(!HasPendingAccept());
     ASSERT(accepted_head_ == NULL);
     ASSERT(accepted_tail_ == NULL);
-  };
+  }
 
   // Socket interface exposing normal socket operations.
   ClientSocket* Accept();
@@ -439,7 +449,7 @@
     ASSERT(!HasPendingWrite());
     ASSERT(next_ == NULL);
     ASSERT(closed_ == true);
-  };
+  }
 
   void Shutdown(int how);
 
@@ -484,7 +494,7 @@
     // Don't delete this object until all pending requests have been handled.
     ASSERT(!HasPendingRead());
     ASSERT(!HasPendingWrite());
-  };
+  }
 
   // Internal interface used by the event handler.
   virtual bool IssueRecvFrom();
diff --git a/runtime/bin/file_patch.dart b/runtime/bin/file_patch.dart
index e41f2f5..ed8e3a0 100644
--- a/runtime/bin/file_patch.dart
+++ b/runtime/bin/file_patch.dart
@@ -124,8 +124,8 @@
       assert(_watcherPath.count > 0);
       _watcherPath.count--;
       if (_watcherPath.count == 0) {
-        _pathWatchedEnd();
         _unwatchPath(_id, _watcherPath.pathId);
+        _pathWatchedEnd();
         _idMap.remove(_watcherPath.pathId);
       }
       _watcherPath = null;
diff --git a/runtime/bin/file_system_watcher_win.cc b/runtime/bin/file_system_watcher_win.cc
index adb2426..f0b71f1 100644
--- a/runtime/bin/file_system_watcher_win.cc
+++ b/runtime/bin/file_system_watcher_win.cc
@@ -73,7 +73,9 @@
 
 void FileSystemWatcher::UnwatchPath(intptr_t id, intptr_t path_id) {
   USE(id);
-  USE(path_id);
+  DirectoryWatchHandle* handle =
+      reinterpret_cast<DirectoryWatchHandle*>(path_id);
+  handle->Stop();
 }
 
 
diff --git a/sdk/lib/_internal/compiler/implementation/inferrer/closure_tracer.dart b/sdk/lib/_internal/compiler/implementation/inferrer/closure_tracer.dart
index 9293b43..52c5dca 100644
--- a/sdk/lib/_internal/compiler/implementation/inferrer/closure_tracer.dart
+++ b/sdk/lib/_internal/compiler/implementation/inferrer/closure_tracer.dart
@@ -25,7 +25,7 @@
         ElementTypeInformation info =
             inferrer.types.getInferredTypeOf(parameter);
         if (continueAnalyzing) {
-          info.disableHandleSpecialCases = true;
+          info.disableInferenceForClosures = false;
         } else {
           info.giveUp(inferrer);
         }
diff --git a/sdk/lib/_internal/compiler/implementation/inferrer/node_tracer.dart b/sdk/lib/_internal/compiler/implementation/inferrer/node_tracer.dart
index e2f8955..e067fe3 100644
--- a/sdk/lib/_internal/compiler/implementation/inferrer/node_tracer.dart
+++ b/sdk/lib/_internal/compiler/implementation/inferrer/node_tracer.dart
@@ -230,7 +230,7 @@
     String selectorName = info.selector.name;
     List<TypeInformation> arguments = info.arguments.positional;
     return (selectorName == '[]=' && currentUser == arguments[1])
-        || (selectorName == 'insert' && currentUser == arguments[0])
+        || (selectorName == 'insert' && currentUser == arguments[1])
         || (selectorName == 'add' && currentUser == arguments[0]);
   }
 
@@ -238,9 +238,7 @@
     if (info.arguments == null) return false;
     var receiverType = info.receiver.type;
     if (!receiverType.isMap) return false;
-    String selectorName = info.selector.name;
-    List<TypeInformation> arguments = info.arguments.positional;
-    return selectorName == '[]=';
+    return info.selector.name == '[]=';
   }
 
   /**
diff --git a/sdk/lib/_internal/compiler/implementation/inferrer/type_graph_nodes.dart b/sdk/lib/_internal/compiler/implementation/inferrer/type_graph_nodes.dart
index fe37c1f..26dd4d1 100644
--- a/sdk/lib/_internal/compiler/implementation/inferrer/type_graph_nodes.dart
+++ b/sdk/lib/_internal/compiler/implementation/inferrer/type_graph_nodes.dart
@@ -226,9 +226,8 @@
 class ElementTypeInformation extends ApplyableTypeInformation  {
   final Element element;
 
-  // Marker to disable [handleSpecialCases]. For example, parameters
-  // of closures that are traced can be inferred.
-  bool disableHandleSpecialCases = false;
+  /// Marker to disable inference for closures in [handleSpecialCases].
+  bool disableInferenceForClosures = true;
 
   /**
    * If [element] is a function, [closurizedCount] is the number of
@@ -293,11 +292,10 @@
 
   TypeMask handleSpecialCases(TypeGraphInferrerEngine inferrer) {
     if (abandonInferencing) return type;
-    if (disableHandleSpecialCases) return null;
 
     if (element.isParameter) {
       Element enclosing = element.enclosingElement;
-      if (Elements.isLocal(enclosing)) {
+      if (Elements.isLocal(enclosing) && disableInferenceForClosures) {
         // Do not infer types for parameters of closures. We do not
         // clear the assignments in case the closure is successfully
         // traced.
@@ -305,7 +303,8 @@
         return type;
       } else if (enclosing.isInstanceMember &&
                  (enclosing.name == Compiler.NO_SUCH_METHOD ||
-                  enclosing.name == Compiler.CALL_OPERATOR_NAME)) {
+                  (enclosing.name == Compiler.CALL_OPERATOR_NAME &&
+                   disableInferenceForClosures))) {
         // Do not infer types for parameters of [noSuchMethod] and
         // [call] instance methods.
         giveUp(inferrer);
diff --git a/tests/compiler/dart2js/closure_tracer_test.dart b/tests/compiler/dart2js/closure_tracer_test.dart
index af72fe0..8d68083 100644
--- a/tests/compiler/dart2js/closure_tracer_test.dart
+++ b/tests/compiler/dart2js/closure_tracer_test.dart
@@ -48,6 +48,46 @@
   return res;
 }
 
+testStoredInMapOfList() {
+  var res;
+  closure(a) => res = a;
+  var a = [closure];
+  var b = {'foo' : 1};
+  b['bar'] = a;
+  b['bar'][0](42);
+  return res;
+}
+
+testStoredInListOfList() {
+  var res;
+  closure(a) => res = a;
+  var a = [closure];
+  var b = [0, 1, 2];
+  b[1] = a;
+  b[1][0](42);
+  return res;
+}
+
+testStoredInListOfListUsingInsert() {
+  var res;
+  closure(a) => res = a;
+  var a = [closure];
+  var b = [0, 1, 2];
+  b.insert(1, a);
+  b[1][0](42);
+  return res;
+}
+
+testStoredInListOfListUsingAdd() {
+  var res;
+  closure(a) => res = a;
+  var a = [closure];
+  var b = [0, 1, 2];
+  b.add(a);
+  b[3][0](42);
+  return res;
+}
+
 foo(closure) {
   closure(42);
 }
@@ -99,6 +139,10 @@
   testFunctionExpression();
   testStoredInStatic();
   testStoredInInstance();
+  testStoredInMapOfList();
+  testStoredInListOfList();
+  testStoredInListOfListUsingInsert();
+  testStoredInListOfListUsingAdd();
   testPassedInParameter();
   testStaticClosure1();
   testStaticClosure2();
@@ -124,6 +168,10 @@
     checkType('testFunctionExpression', typesTask.uint31Type);
     checkType('testStoredInInstance', typesTask.uint31Type);
     checkType('testStoredInStatic', typesTask.uint31Type);
+    checkType('testStoredInMapOfList', typesTask.uint31Type);
+    checkType('testStoredInListOfList', typesTask.uint31Type);
+    checkType('testStoredInListOfListUsingInsert', typesTask.uint31Type);
+    checkType('testStoredInListOfListUsingAdd', typesTask.uint31Type);
     checkType('testPassedInParameter', typesTask.uint31Type);
     checkType('testStaticClosure1', typesTask.uint31Type);
     checkType('testStaticClosure2', typesTask.numType);
diff --git a/tools/VERSION b/tools/VERSION
index 3bf5b0d..a86f573 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
 MINOR 5
 PATCH 0
 PRERELEASE 4
-PRERELEASE_PATCH 0
+PRERELEASE_PATCH 1