Rework file notifications, kqueue has problems with directory monitors

* lisp/filenotify.el (file-notify-add-watch): Call the native
add-watch function on the file, not on the dir.

* src/kqueue.c (kqueue_compare_dir_list): Make also bookkeeping
about already deleted entries.

* test/automated/auto-revert-tests.el
(auto-revert-test01-auto-revert-several-files): Do not call "cp -f"
since this deletes the target file first.

* test/automated/file-notify-tests.el (file-notify--test-event-test):
Make stronger checks.
(file-notify-test01-add-watch, file-notify-test02-events)
(file-notify-test04-file-validity, file-notify-test05-dir-validity):
Rewrite in order to call file monitors but directory monitors.
(file-notify-test06-many-events): Ler rename work in both directions.
This commit is contained in:
Michael Albinus 2015-11-20 18:06:42 +00:00
parent 5154781141
commit 0247489fed
4 changed files with 203 additions and 148 deletions

View file

@ -236,7 +236,7 @@ EVENT is the cadr of the event in `file-notify-handle-event'
(setq pending-event nil))
;; Check for stopped.
;;(message "file-notify-callback %S %S" file registered)
;;(message "file-notify-callback %S %S %S" file file1 registered)
(setq
stopped
(or
@ -342,7 +342,7 @@ FILE is the name of the file whose event is being reported."
;; A file name handler could exist even if there is no local
;; file notification support.
(setq desc (funcall
handler 'file-notify-add-watch dir flags callback))
handler 'file-notify-add-watch file flags callback))
;; Check, whether Emacs has been compiled with file notification
;; support.
@ -379,7 +379,7 @@ FILE is the name of the file whose event is being reported."
l-flags)))
;; Call low-level function.
(setq desc (funcall func dir l-flags 'file-notify-callback)))
(setq desc (funcall func file l-flags 'file-notify-callback)))
;; Modify `file-notify-descriptors'.
(setq file (unless (file-directory-p file) (file-name-nondirectory file))

View file

@ -111,11 +111,12 @@ static void
kqueue_compare_dir_list
(Lisp_Object watch_object)
{
Lisp_Object dir, pending_events;
Lisp_Object dir, pending_dl, deleted_dl;
Lisp_Object old_directory_files, old_dl, new_directory_files, new_dl, dl;
dir = XCAR (XCDR (watch_object));
pending_events = Qnil;
pending_dl = Qnil;
deleted_dl = Qnil;
old_directory_files = Fnth (make_number (4), watch_object);
old_dl = kqueue_directory_listing (old_directory_files);
@ -168,6 +169,7 @@ kqueue_compare_dir_list
kqueue_generate_event
(watch_object, Fcons (Qrename, Qnil),
XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)));
deleted_dl = Fcons (new_entry, deleted_dl);
}
new_dl = Fdelq (new_entry, new_dl);
goto the_end;
@ -179,24 +181,35 @@ kqueue_compare_dir_list
new_entry = XCAR (dl1);
if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
SSDATA (XCAR (XCDR (new_entry)))) == 0) {
pending_events = Fcons (new_entry, pending_events);
pending_dl = Fcons (new_entry, pending_dl);
new_dl = Fdelq (new_entry, new_dl);
goto the_end;
}
}
new_entry = assq_no_quit (XCAR (old_entry), pending_events);
if (NILP (new_entry))
/* Check, whether this a pending file. */
new_entry = assq_no_quit (XCAR (old_entry), pending_dl);
if (NILP (new_entry)) {
/* Check, whether this is an already deleted file (by rename). */
for (dl1 = deleted_dl; ! NILP (dl1); dl1 = XCDR (dl1)) {
new_entry = XCAR (dl1);
if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
SSDATA (XCAR (XCDR (new_entry)))) == 0) {
deleted_dl = Fdelq (new_entry, deleted_dl);
goto the_end;
}
}
/* The file has been deleted. */
kqueue_generate_event
(watch_object, Fcons (Qdelete, Qnil), XCAR (XCDR (old_entry)), Qnil);
else {
} else {
/* The file has been renamed. */
kqueue_generate_event
(watch_object, Fcons (Qrename, Qnil),
XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)));
new_dl = Fdelq (new_entry, new_dl);
pending_events = Fdelq (new_entry, pending_events);
pending_dl = Fdelq (new_entry, pending_dl);
}
the_end:
@ -226,8 +239,8 @@ kqueue_compare_dir_list
new_dl = Fdelq (entry, new_dl);
}
/* Parse through the resulting pending_events_list. */
dl = pending_events;
/* Parse through the resulting pending_dl list. */
dl = pending_dl;
while (1) {
Lisp_Object entry;
if (NILP (dl))
@ -239,18 +252,21 @@ kqueue_compare_dir_list
(watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (entry)), Qnil);
dl = XCDR (dl);
pending_events = Fdelq (entry, pending_events);
pending_dl = Fdelq (entry, pending_dl);
}
/* At this point, old_dl, new_dl and pending_events shall be empty.
Let's make a check for this (might be removed once the code is
stable). */
/* At this point, old_dl, new_dl and pending_dl shall be empty.
deleted_dl might not be empty when there was a rename to a
nonexisting file. Let's make a check for this (might be removed
once the code is stable). */
if (! NILP (old_dl))
report_file_error ("Old list not empty", old_dl);
if (! NILP (new_dl))
report_file_error ("New list not empty", new_dl);
if (! NILP (pending_events))
report_file_error ("Pending events not empty", new_dl);
if (! NILP (pending_dl))
report_file_error ("Pending events list not empty", pending_dl);
// if (! NILP (deleted_dl))
// report_file_error ("Deleted events list not empty", deleted_dl);
/* Replace old directory listing with the new one. */
XSETCDR (Fnthcdr (make_number (3), watch_object),

View file

@ -136,7 +136,7 @@
;; Strange, that `copy-directory' does not work as expected.
;; The following shell command is not portable on all
;; platforms, unfortunately.
(shell-command (format "%s -f %s/* %s" cp tmpdir2 tmpdir1))
(shell-command (format "%s %s/* %s" cp tmpdir2 tmpdir1))
;; Check, that the buffers have been reverted.
(dolist (buf (list buf1 buf2))

View file

@ -196,12 +196,13 @@ remote host, or nil."
(file-notify-add-watch
temporary-file-directory '(change attribute-change) 'ignore)))
(file-notify-rm-watch file-notify--test-desc)
;; The file does not need to exist, just the upper directory.
(write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
(should
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile '(change attribute-change) 'ignore)))
(file-notify-rm-watch file-notify--test-desc)
(delete-file file-notify--test-tmpfile)
;; Check error handling.
(should-error (file-notify-add-watch 1 2 3 4)
@ -242,16 +243,17 @@ is bound somewhere."
(should
(or (string-equal (file-notify--event-file-name file-notify--test-event)
file-notify--test-tmpfile)
(string-equal (directory-file-name
(file-name-directory
(file-notify--event-file-name file-notify--test-event)))
file-notify--test-tmpfile)))
(string-equal (file-notify--event-file-name file-notify--test-event)
file-notify--test-tmpfile1)
(string-equal (file-notify--event-file-name file-notify--test-event)
temporary-file-directory)))
;; Check the second file name if exists.
(when (eq (nth 1 file-notify--test-event) 'renamed)
(should
(string-equal
(file-notify--event-file1-name file-notify--test-event)
file-notify--test-tmpfile1))))
(or (string-equal (file-notify--event-file1-name file-notify--test-event)
file-notify--test-tmpfile1)
(string-equal (file-notify--event-file1-name file-notify--test-event)
temporary-file-directory)))))
(defun file-notify--test-event-handler (event)
"Run a test over FILE-NOTIFY--TEST-EVENT.
@ -306,103 +308,111 @@ Don't wait longer than timeout seconds for the events to be delivered."
;; Under cygwin there are so bad timings that it doesn't make sense to test.
(skip-unless (not (eq system-type 'cygwin)))
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
file-notify--test-tmpfile1 (file-notify--test-make-temp-name))
(unwind-protect
(progn
;; Check creation, change and deletion.
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) 'file-notify--test-event-handler))
(file-notify--test-with-events '(created changed deleted)
;; Check file change and deletion.
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
(write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
(should
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) 'file-notify--test-event-handler)))
(file-notify--test-with-events '(changed deleted)
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
(read-event nil nil 0.1)
"another text" nil file-notify--test-tmpfile nil 'no-message)
(delete-file file-notify--test-tmpfile))
;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
(let (file-notify--test-events)
(file-notify-rm-watch file-notify--test-desc))
;; Check creation, change and deletion. There must be a
;; `stopped' event when deleting the directory. It doesn't
;; work for w32notify.
;; Check file creation, change and deletion when watching a
;; directory. There must be a `stopped' event when deleting
;; the directory. It doesn't work for w32notify.
(unless (string-equal (file-notify--test-library) "w32notify")
(make-directory file-notify--test-tmpfile)
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) 'file-notify--test-event-handler))
(let ((temporary-file-directory
(make-temp-file "file-notify-test-parent" t)))
(should
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
file-notify--test-desc
(file-notify-add-watch
temporary-file-directory
'(change) 'file-notify--test-event-handler)))
(file-notify--test-with-events
;; There are two `deleted' events, for the file and
;; for the directory. Except for kqueue.
(if (string-equal (file-notify--test-library) "kqueue")
'(created changed deleted stopped)
'(created changed deleted deleted stopped))
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
(read-event nil nil 0.1)
(delete-directory temporary-file-directory 'recursive))
;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
(let (file-notify--test-events)
(file-notify-rm-watch file-notify--test-desc))))
;; Check copy of files inside a directory.
(let ((temporary-file-directory
(make-temp-file "file-notify-test-parent" t)))
(should
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
file-notify--test-desc
(file-notify-add-watch
temporary-file-directory
'(change) 'file-notify--test-event-handler)))
(file-notify--test-with-events
;; There are two `deleted' events, for the file and for
;; the directory. Except for kqueue.
(if (string-equal (file-notify--test-library) "kqueue")
'(created changed deleted stopped)
'(created changed deleted deleted stopped))
;; w32notify does not distinguish between `changed' and
;; `attribute-changed'.
(if (string-equal (file-notify--test-library) "w32notify")
'(created changed changed deleted)
'(created changed created changed deleted stopped))
(write-region
"any text" nil (expand-file-name "foo" file-notify--test-tmpfile)
nil 'no-message)
"any text" nil file-notify--test-tmpfile nil 'no-message)
(read-event nil nil 0.1)
(delete-directory file-notify--test-tmpfile 'recursive))
(copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
;; The next two events shall not be visible.
(read-event nil nil 0.1)
(set-file-modes file-notify--test-tmpfile 000)
(read-event nil nil 0.1)
(set-file-times file-notify--test-tmpfile '(0 0))
(read-event nil nil 0.1)
(delete-directory temporary-file-directory 'recursive))
;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
(let (file-notify--test-events)
(file-notify-rm-watch file-notify--test-desc)))
;; Check copy.
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) 'file-notify--test-event-handler))
(should file-notify--test-desc)
(file-notify--test-with-events
;; w32notify does not distinguish between `changed' and
;; `attribute-changed'.
(if (string-equal (file-notify--test-library) "w32notify")
'(created changed changed deleted)
'(created changed deleted))
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
(read-event nil nil 0.1)
(copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
;; The next two events shall not be visible.
(read-event nil nil 0.1)
(set-file-modes file-notify--test-tmpfile 000)
(read-event nil nil 0.1)
(set-file-times file-notify--test-tmpfile '(0 0))
(read-event nil nil 0.1)
(delete-file file-notify--test-tmpfile)
(read-event nil nil 0.1)
(delete-file file-notify--test-tmpfile1))
;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
(let (file-notify--test-events)
(file-notify-rm-watch file-notify--test-desc))
;; Check rename.
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) 'file-notify--test-event-handler))
(should file-notify--test-desc)
(file-notify--test-with-events '(created changed renamed)
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
(read-event nil nil 0.1)
(rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
;; After the rename, we won't get events anymore.
(read-event nil nil 0.1)
(delete-file file-notify--test-tmpfile1))
;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
(let (file-notify--test-events)
(file-notify-rm-watch file-notify--test-desc))
;; Check rename of files inside a directory.
(let ((temporary-file-directory
(make-temp-file "file-notify-test-parent" t)))
(should
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
file-notify--test-desc
(file-notify-add-watch
temporary-file-directory
'(change) 'file-notify--test-event-handler)))
(file-notify--test-with-events '(created changed renamed)
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
(read-event nil nil 0.1)
(rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
;; After the rename, we won't get events anymore.
(read-event nil nil 0.1)
(delete-directory temporary-file-directory 'recursive))
;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
(let (file-notify--test-events)
(file-notify-rm-watch file-notify--test-desc)))
;; Check attribute change. It doesn't work for kqueue and w32notify.
(unless (or (string-equal (file-notify--test-library) "kqueue")
(string-equal (file-notify--test-library) "w32notify"))
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(attribute-change) 'file-notify--test-event-handler))
(should
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(attribute-change) 'file-notify--test-event-handler)))
(file-notify--test-with-events
(if (file-remote-p temporary-file-directory)
;; In the remote case, `write-region' raises also an
@ -533,23 +543,41 @@ Don't wait longer than timeout seconds for the events to be delivered."
(unwind-protect
(progn
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) #'file-notify--test-event-handler))
(file-notify--test-with-events '(created changed deleted)
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
(write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
(should
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) #'file-notify--test-event-handler)))
(should (file-notify-valid-p file-notify--test-desc))
;; After calling `file-notify-rm-watch', the descriptor is not
;; valid anymore.
(file-notify-rm-watch file-notify--test-desc)
(should-not (file-notify-valid-p file-notify--test-desc))
(delete-file file-notify--test-tmpfile))
;; Cleanup.
(file-notify--test-cleanup))
(unwind-protect
(progn
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
(write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
(should
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) #'file-notify--test-event-handler)))
(file-notify--test-with-events '(changed deleted)
(should (file-notify-valid-p file-notify--test-desc))
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
"another text" nil file-notify--test-tmpfile nil 'no-message)
(read-event nil nil 0.1)
(delete-file file-notify--test-tmpfile))
;; After deleting the file, the descriptor is still valid.
(should (file-notify-valid-p file-notify--test-desc))
;; After removing the watch, the descriptor must not be valid
;; anymore.
(file-notify-rm-watch file-notify--test-desc)
(should-not (file-notify-valid-p file-notify--test-desc)))
;; After deleting the file, the descriptor is not valid anymore.
(should-not (file-notify-valid-p file-notify--test-desc))
(file-notify-rm-watch file-notify--test-desc))
;; Cleanup.
(file-notify--test-cleanup))
@ -560,11 +588,12 @@ Don't wait longer than timeout seconds for the events to be delivered."
(unless (string-equal (file-notify--test-library) "w32notify")
(let ((temporary-file-directory
(make-temp-file "file-notify-test-parent" t)))
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) #'file-notify--test-event-handler))
(should
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
file-notify--test-desc
(file-notify-add-watch
temporary-file-directory
'(change) #'file-notify--test-event-handler)))
(file-notify--test-with-events
;; There are two `deleted' events, for the file and for
;; the directory. Except for kqueue.
@ -595,10 +624,11 @@ Don't wait longer than timeout seconds for the events to be delivered."
(setq file-notify--test-tmpfile
(file-name-as-directory (file-notify--test-make-temp-name)))
(make-directory file-notify--test-tmpfile)
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) #'file-notify--test-event-handler))
(should
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) #'file-notify--test-event-handler)))
(should (file-notify-valid-p file-notify--test-desc))
;; After removing the watch, the descriptor must not be valid
;; anymore.
@ -619,10 +649,11 @@ Don't wait longer than timeout seconds for the events to be delivered."
(setq file-notify--test-tmpfile
(file-name-as-directory (file-notify--test-make-temp-name)))
(make-directory file-notify--test-tmpfile)
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) #'file-notify--test-event-handler))
(should
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) #'file-notify--test-event-handler)))
(should (file-notify-valid-p file-notify--test-desc))
;; After deleting the directory, the descriptor must not be
;; valid anymore.
@ -645,31 +676,39 @@ Don't wait longer than timeout seconds for the events to be delivered."
(skip-unless (not (eq system-type 'cygwin)))
(setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
(make-directory file-notify--test-tmpfile)
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) 'file-notify--test-event-handler))
(should
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile
'(change) 'file-notify--test-event-handler)))
(unwind-protect
(let ((n 1000)
x-file-list y-file-list
source-file-list target-file-list
(default-directory file-notify--test-tmpfile))
(dotimes (i n)
(push (expand-file-name (format "x%d" i)) x-file-list)
(push (expand-file-name (format "y%d" i)) y-file-list))
;; It matters which direction we rename, at least for
;; kqueue. This backend parses directories in alphabetic
;; order (x%d before y%d). So we rename both directions.
(if (zerop (mod i 2))
(progn
(push (expand-file-name (format "x%d" i)) source-file-list)
(push (expand-file-name (format "y%d" i)) target-file-list))
(push (expand-file-name (format "y%d" i)) source-file-list)
(push (expand-file-name (format "x%d" i)) target-file-list)))
(file-notify--test-with-events (make-list (+ n n) 'created)
(let ((x-file-list x-file-list)
(y-file-list y-file-list))
(while (and x-file-list y-file-list)
(write-region "" nil (pop x-file-list) nil 'no-message)
(let ((source-file-list source-file-list)
(target-file-list target-file-list))
(while (and source-file-list target-file-list)
(write-region "" nil (pop source-file-list) nil 'no-message)
(read-event nil nil 0.1)
(write-region "" nil (pop y-file-list) nil 'no-message))))
(write-region "" nil (pop target-file-list) nil 'no-message))))
(file-notify--test-with-events (make-list n 'renamed)
(let ((x-file-list x-file-list)
(y-file-list y-file-list))
(while (and x-file-list y-file-list)
(rename-file (pop x-file-list) (pop y-file-list) t))))
(let ((source-file-list source-file-list)
(target-file-list target-file-list))
(while (and source-file-list target-file-list)
(rename-file (pop source-file-list) (pop target-file-list) t))))
(file-notify--test-with-events (make-list n 'deleted)
(dolist (file y-file-list)
(dolist (file target-file-list)
(delete-file file))))
(file-notify--test-cleanup)))