Return a list of watched paths that where affected by recent
        changes, following a successful poll() return for the controlling
        file descriptor.

        If param "timeout" is greater than 0, the event queue will be read
        multiple times and reads continue until a timeout occurs.

        With a timeout active, if param "limit" is greater than 0,
        event reads will stop when the number of changes exceeds the
        limit.  This guarantees that the time the method will block
        will never be greater than timeout*limit seconds.

        Note that with a timeout active, multiple changes to a
        single path will only be reported once.

def get(self, **params):
        """
        Return a list of watched paths that where affected by recent
        changes, following a successful poll() return for the controlling
        file descriptor.

        If param "timeout" is greater than 0, the event queue will be read
        multiple times and reads continue until a timeout occurs.

        With a timeout active, if param "limit" is greater than 0,
        event reads will stop when the number of changes exceeds the
        limit.  This guarantees that the time the method will block
        will never be greater than timeout*limit seconds.

        Note that with a timeout active, multiple changes to a
        single path will only be reported once.
    """
        log = self._getparam('log', self._discard, **params)

        self.last_changes = {}

        timeout = self._getparam('timeout', 0, **params)
        if not timeout or timeout < 0:
            timeout = 0
        limit = self._getparam('limit', None, **params)
        if not limit or limit < 0:
            limit = None

        max_events = limit if limit else 10000
        if self.unprocessed_event:
            log.debug("Will handle unprocessed event")

        if self._mode == WF_KQUEUE:
            evagg = {}
            while True:
                try:
                    evlist = self._kq.control(None, max_events, timeout)
                except OSError as e:
                    if e.errno == errno.EINTR:
                        break
                    raise e
                if not evlist:
                    break

                log.debug("kq.control() returned %d event%s", len(evlist), ses(len(evlist)))
                for ev in evlist:
                    if ev.ident in self.fds_open:
                        path = self.fds_open[ev.ident]
                        if path in evagg:
                            evagg[path].fflags |= ev.fflags
                        else:
                            evagg[path] = ev
                if limit and len(evagg) >= limit:
                    break
            for path, ev in evagg.items():
                if ev.fflags & (select.KQ_NOTE_DELETE | select.KQ_NOTE_RENAME):
                    self._disappeared(ev.ident, path, **params)
                self.last_changes[path] = time.time()
                log.debug("Change on %r", path)

        elif self._mode == WF_INOTIFYX:
            evagg = {}
            while True:
                try:
                    evlist = pynotifyx.get_events(self._inx_fd, timeout)
                except IOError as e:
                    if e.errno == errno.EINTR:
                        break
                    raise e
                if not evlist:
                    break

                log.debug("pynotifyx.get_events() returned %d event%s", len(evlist), ses(len(evlist)))

                for ev in evlist:
                    if ev.wd in self.fds_open:
                        path = self.fds_open[ev.wd]
                        if path in evagg:
                            evagg[path].mask |= ev.mask
                        else:
                            evagg[path] = ev
                    elif ev.mask & pynotifyx.IN_IGNORED:
                        log.debug("skipping IN_IGNORED event on unknown wd %d", ev.wd)
                    else:
                        log.warning("attempt to handle unknown inotify event wd %d", ev.wd)
                if limit and len(evagg) >= limit:
                    break
            for path, ev in evagg.items():
                log.debug("Change on %r -- %s", path, ev.get_mask_description())
                if ev.mask & (pynotifyx.IN_DELETE_SELF | pynotifyx.IN_MOVE_SELF):
                    self._disappeared(ev.wd, path, **params)
                elif ev.mask & pynotifyx.IN_ATTRIB:
                    file_move_del = False
                    try:
                        s = os.stat(path)
                        if s.st_ino != self._inx_inode[path]:
                            file_move_del = True
                            log.info("'simfs' (used with containers) bug detected -- %r moved", path)
                    except Exception as e:
                        file_move_del = True
                        log.info("'simfs' (used with containers) bug detected -- %r removed", path)
                    if file_move_del:
                        self._disappeared(ev.wd, path, **params)
                self.last_changes[path] = time.time()

        elif self._mode == WF_POLLING:
            #  Consume any pending data from the self-pipe.  Read
            #  until EOF.  The fd is already non-blocking so this
            #  terminates on zero read or any error.
            #
            cnt = 0
            while True:
                try:
                    data = os.read(self._poll_fd, 1024)
                    if data == '':
                        break
                    cnt += len(data)
                except OSError as e:
                    if e.errno != errno.EAGAIN:
                        log.warning("Ignoring self-pipe read failure -- %s", e)
                    break
                except Exception as e:
                    log.warning("Ignoring self-pipe read failure -- %s", e)
                    break
            log.debug("Self-pipe read consumed %d byte%s", cnt, ses(cnt))
            now = time.time()
            for path in self._poll_pending:
                self.last_changes[path] = self._poll_pending[path]
            self._poll_pending = {}
            for fd in list(self._poll_stat):
                path = self.fds_open[fd]
                fstate = self._poll_get_stat(fd, path)
                if fstate is None:
                    self.last_changes[path] = now
                elif self._poll_stat[fd] != fstate:
                    self._poll_stat[fd] = fstate
                    self.last_changes[path] = now
                    log.debug("Change on %r", path)
        else:
            raise Exception("Unsupported polling mode " + self.get_mode_name())
        paths = list(self.last_changes)
        paths.sort()
        log.debug("Change was to %d path%s", len(paths), ses(len(paths)))
        return paths