Commit c84b4fbb by rfkelly0

watch_win32: use weakrefs to avoid immortal reference cycles

parent be7b34bb
...@@ -45,6 +45,12 @@ ...@@ -45,6 +45,12 @@
* Fixed operation of OSFS on win32 when it points to the root of a drive. * Fixed operation of OSFS on win32 when it points to the root of a drive.
* Made SubFS a subclass of WrapFS, and moved it into its own module at * Made SubFS a subclass of WrapFS, and moved it into its own module at
fs.wrapfs.subfs. fs.wrapfs.subfs.
* OSFSWatchMixin improvements:
* ensure that immortal reference cycles aren't created.
* watch_inotify: allow more than one watcher on a single path.
* watch_win32: report errors if the filesystem does't support
ReadDirectoryChangesW.
* watch_win32: don't create immortal reference cycles.
* Fix OSFS.add_watcher on linux platforms; previous version would cancel * Fix OSFS.add_watcher on linux platforms; previous version would cancel
any watchers when a new one was added. any watchers when a new one was added.
* MountFS: added support for mounting at the root directory, and for * MountFS: added support for mounting at the root directory, and for
......
...@@ -16,6 +16,7 @@ import struct ...@@ -16,6 +16,7 @@ import struct
import ctypes import ctypes
import ctypes.wintypes import ctypes.wintypes
import traceback import traceback
import weakref
try: try:
LPVOID = ctypes.wintypes.LPVOID LPVOID = ctypes.wintypes.LPVOID
...@@ -192,6 +193,7 @@ class WatchedDirectory(object): ...@@ -192,6 +193,7 @@ class WatchedDirectory(object):
self.callback = callback self.callback = callback
self.recursive = recursive self.recursive = recursive
self.handle = None self.handle = None
self.error = None
self.handle = CreateFileW(path, self.handle = CreateFileW(path,
FILE_LIST_DIRECTORY, FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
...@@ -219,9 +221,14 @@ class WatchedDirectory(object): ...@@ -219,9 +221,14 @@ class WatchedDirectory(object):
overlapped.OffsetHigh = 0 overlapped.OffsetHigh = 0
overlapped.Pointer = 0 overlapped.Pointer = 0
overlapped.hEvent = 0 overlapped.hEvent = 0
try:
ReadDirectoryChangesW(self.handle, ReadDirectoryChangesW(self.handle,
ctypes.byref(self.result),len(self.result), ctypes.byref(self.result),len(self.result),
self.recursive,self.flags,None,overlapped,None) self.recursive,self.flags,None,
overlapped,None)
except WindowsError, e:
self.error = e
self.close()
def complete(self,nbytes): def complete(self,nbytes):
if nbytes == 0: if nbytes == 0:
...@@ -370,7 +377,10 @@ class WatchThread(threading.Thread): ...@@ -370,7 +377,10 @@ class WatchThread(threading.Thread):
try: try:
while True: while True:
w = self._new_watches.get_nowait() w = self._new_watches.get_nowait()
CreateIoCompletionPort(w.handle,self._iocp,hash(w),0) if w.handle is not None:
CreateIoCompletionPort(w.handle,
self._iocp,
hash(w),0)
w.post() w.post()
w.ready.set() w.ready.set()
except Queue.Empty: except Queue.Empty:
...@@ -405,17 +415,26 @@ class OSFSWatchMixin(WatchableFSMixin): ...@@ -405,17 +415,26 @@ class OSFSWatchMixin(WatchableFSMixin):
w = super(OSFSWatchMixin,self).add_watcher(callback,path,events,recursive) w = super(OSFSWatchMixin,self).add_watcher(callback,path,events,recursive)
syspath = self.getsyspath(path) syspath = self.getsyspath(path)
wt = self.__get_watch_thread() wt = self.__get_watch_thread()
# Careful not to create a reference cycle here.
weak_self = weakref.ref(self)
def handle_event(event_class,path,*args,**kwds): def handle_event(event_class,path,*args,**kwds):
selfref = weak_self()
if selfref is None:
return
try: try:
path = self.unsyspath(path) path = selfref.unsyspath(path)
except ValueError: except ValueError:
pass pass
else: else:
if event_class in (MOVED_SRC,MOVED_DST) and args and args[0]: if event_class in (MOVED_SRC,MOVED_DST) and args and args[0]:
args = (self.unsyspath(args[0]),) + args[1:] args = (selfref.unsyspath(args[0]),) + args[1:]
event = event_class(self,path,*args,**kwds) event = event_class(selfref,path,*args,**kwds)
w.handle_event(event) w.handle_event(event)
w._watch_objs = wt.add_watcher(handle_event,syspath,w.events,w.recursive) w._watch_objs = wt.add_watcher(handle_event,syspath,w.events,w.recursive)
for wd in w._watch_objs:
if wd.error is not None:
self.del_watcher(w)
raise wd.error
return w return w
@convert_os_errors @convert_os_errors
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
import os import os
import sys import sys
import time import time
import gc
import pickle
import unittest import unittest
from fs.path import * from fs.path import *
...@@ -176,6 +178,7 @@ class WatcherTestCases: ...@@ -176,6 +178,7 @@ class WatcherTestCases:
changes.close() changes.close()
from fs import tempfs, osfs from fs import tempfs, osfs
class TestWatchers_TempFS(unittest.TestCase,FSTestCases,WatcherTestCases): class TestWatchers_TempFS(unittest.TestCase,FSTestCases,WatcherTestCases):
......
...@@ -21,6 +21,12 @@ An FS object that wants to be "watchable" must provide the following methods: ...@@ -21,6 +21,12 @@ An FS object that wants to be "watchable" must provide the following methods:
Remove the given watcher object, or any watchers associated with Remove the given watcher object, or any watchers associated with
the given callback. the given callback.
If you would prefer to read changes from a filesystem in a blocking fashion
rather than using callbacks, you can use the function 'iter_changes' to obtain
an iterator over the change events.
""" """
import weakref import weakref
...@@ -554,15 +560,9 @@ def ensure_watchable(fs,wrapper_class=PollingWatchableFS,*args,**kwds): ...@@ -554,15 +560,9 @@ def ensure_watchable(fs,wrapper_class=PollingWatchableFS,*args,**kwds):
if isinstance(fs,wrapper_class): if isinstance(fs,wrapper_class):
return fs return fs
try: try:
# Try to add a watcher to a path that's unlikely to exist. w = fs.add_watcher(lambda e: None,"/",recursive=False)
# It's OK if the path does exist, since we remove the watcher. except (AttributeError,FSError):
# It just might be slightly slower as the fs will actually have
# to establish the watcher.
w = fs.add_watcher(lambda e: None,"/somepaththatsnotlikelytoexist")
except (AttributeError,UnsupportedError):
return wrapper_class(fs,*args,**kwds) return wrapper_class(fs,*args,**kwds)
except FSError:
pass
else: else:
fs.del_watcher(w) fs.del_watcher(w)
return fs return fs
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment