Skip to content

Commit

Permalink
Fix garbage collection of Notifier
Browse files Browse the repository at this point in the history
This uses a weak reference to the notifier in _SysProcessEvent and adds
a `__del__` method for Notifier.

This prevents pyinotify from leaking inotify file descriptors
(instances) and its watches.

Fixes seb-m#95

I have used the following script to test it:

    from __future__ import print_function

    import gc
    import os

    from pyinotify import Notifier, WatchManager

    def has_inotify_fds():
        "Does the current process have inotify fds?"
        r = 0
        for x in os.walk('/proc/{}/fd'.format(os.getpid())):
            for y in x[2]:
                path = os.path.join(x[0], y)
                if os.path.exists(path):
                    if 'inotify' in os.path.join(os.readlink(path)):
                        r += 1
        return r

    gc.disable()

    wm = WatchManager()
    n = Notifier(wm)
    assert has_inotify_fds() == 1

    # Stopping multiple times should work:
    n.stop()
    assert has_inotify_fds() == 0
    n.stop()

    gc.collect()
    # XXX: needs a new WatchManager, because the previous fd gets closed by stop!
    wm = WatchManager()
    n = Notifier(wm)
    assert has_inotify_fds() == 1

    # Collect anything.
    gc.collect()
    assert n in gc.get_objects()

    n = None
    assert gc.garbage == []
    assert not any(isinstance(x, Notifier) for x in gc.garbage)
    assert not any(isinstance(x, Notifier) for x in gc.get_objects())

    assert has_inotify_fds() == 0
  • Loading branch information
blueyed committed Mar 16, 2015
1 parent f3ee9bb commit 89a88f6
Show file tree
Hide file tree
Showing 2 changed files with 12 additions and 4 deletions.
8 changes: 6 additions & 2 deletions python2/pyinotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def __init__(self, version):
import re
import asyncore
import subprocess
import weakref

try:
from functools import reduce
Expand Down Expand Up @@ -708,7 +709,7 @@ def __init__(self, wm, notifier):
@type notifier: Notifier instance
"""
self._watch_manager = wm # watch manager
self._notifier = notifier # notifier
self._notifier = weakref.ref(notifier) # notifier
self._mv_cookie = {} # {cookie(int): (src_path(str), date), ...}
self._mv = {} # {src_path(str): (dst_path(str), date), ...}

Expand Down Expand Up @@ -766,7 +767,7 @@ def process_IN_CREATE(self, raw_event):
# This path should not be taken.
continue
rawevent = _RawEvent(created_dir_wd, flags, 0, name)
self._notifier.append_event(rawevent)
self._notifier().append_event(rawevent)
except OSError, err:
msg = "process_IN_CREATE, invalid directory %s: %s"
log.debug(msg % (created_dir, str(err)))
Expand Down Expand Up @@ -1191,6 +1192,9 @@ def __init__(self, watch_manager, default_proc_fun=None, read_freq=0,
# set of str(raw_event), only used when coalesce option is True
self._eventset = set()

def __del__(self):
self.stop()

def append_event(self, event):
"""
Append a raw event to the event queue.
Expand Down
8 changes: 6 additions & 2 deletions python3/pyinotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def __init__(self, version):
import glob
import locale
import subprocess
import weakref

try:
from functools import reduce
Expand Down Expand Up @@ -698,7 +699,7 @@ def __init__(self, wm, notifier):
@type notifier: Notifier instance
"""
self._watch_manager = wm # watch manager
self._notifier = notifier # notifier
self._notifier = weakref.ref(notifier) # notifier
self._mv_cookie = {} # {cookie(int): (src_path(str), date), ...}
self._mv = {} # {src_path(str): (dst_path(str), date), ...}

Expand Down Expand Up @@ -756,7 +757,7 @@ def process_IN_CREATE(self, raw_event):
# This path should not be taken.
continue
rawevent = _RawEvent(created_dir_wd, flags, 0, name)
self._notifier.append_event(rawevent)
self._notifier().append_event(rawevent)
except OSError as err:
msg = "process_IN_CREATE, invalid directory: %s"
log.debug(msg % str(err))
Expand Down Expand Up @@ -1181,6 +1182,9 @@ def __init__(self, watch_manager, default_proc_fun=None, read_freq=0,
# set of str(raw_event), only used when coalesce option is True
self._eventset = set()

def __del__(self):
self.stop()

def append_event(self, event):
"""
Append a raw event to the event queue.
Expand Down

0 comments on commit 89a88f6

Please sign in to comment.