Don't poll auto-revert files that use notification (bug#35418)

It is a waste to periodically poll files that use change notification
in auto-revert mode; stop doing that.  If no files need polling,
turn off the periodic execution entirely to further avoid wasting power.
Use a timer to inhibit immediate reversion for some time after a
notification, for throttling.

This change does not apply to files in global-auto-revert-mode, where
polling is still necessary.  It is disabled by default, and enabled by
setting `auto-revert-avoid-polling' to non-nil.

* lisp/autorevert.el
(toplevel): Require cl-lib.
(auto-revert-avoid-polling, auto-revert--polled-buffers)
(auto-revert--need-polling-p, auto-revert--lockout-interval)
(auto-revert--lockout-timer, auto-revert--end-lockout): New.
(global-auto-revert-mode): Keep notifiers for buffers in auto-revert mode.
(auto-revert-set-timer): Use auto-revert--need-polling-p.
(auto-revert-notify-handler): Restart polling if notification stopped.
Use new lockout timer.
(auto-revert-buffers):
Use auto-revert--polled-buffers and auto-revert--need-polling-p.
(auto-revert-buffers-counter, auto-revert-buffers-counter-lockedout):
Remove.

* etc/NEWS (Changes in Specialized Modes and Packages):
Describe the new auto-revert-avoid-polling variable.

* doc/emacs/files.texi (Reverting):
Add paragraph describing auto-revert-avoid-polling.
This commit is contained in:
Mattias Engdegård 2019-04-24 18:39:05 +02:00
parent f478082f9f
commit c61bbb4c8e
3 changed files with 109 additions and 35 deletions

View file

@ -988,6 +988,20 @@ the polling interval through the variable @code{auto-revert-interval}.
supported, @code{auto-revert-use-notify} will be @code{nil} by
default.
@vindex auto-revert-avoid-polling
@vindex auto-revert-notify-exclude-dir-regexp
By default, Auto-Revert mode will poll files for changes
periodically even when file notifications are used. Such polling is
usually unnecessary, and turning it off may save power by relying on
notifications only. To do so, set the variable
@code{auto-revert-avoid-polling} to non-@code{nil}. However,
notification is ineffective on certain file systems; mainly network
file system on Unix-like machines, where files can be altered from
other machines. To force polling when
@code{auto-revert-avoid-polling} is non-@code{nil}, set
@code{auto-revert-notify-exclude-dir-regexp} to match files that
should be excluded from using notification.
One use of Auto-Revert mode is to ``tail'' a file such as a system
log, so that changes made to that file by other programs are
continuously displayed. To do this, just move the point to the end of

View file

@ -1389,6 +1389,16 @@ Packages deriving from 'js-mode' with 'define-derived-mode' should
call this function to add enabled syntax extensions to their mode
name, too.
** Autorevert
*** New variable 'auto-revert-avoid-polling' for saving power.
When set to a non-nil value, buffers in Auto-Revert mode are no longer
polled for changes periodically. This reduces the power consumption
of an idle Emacs, but may fail on some network file systems; set
'auto-revert-notify-exclude-dir-regexp' to match files where
notification is not supported. The new variable currently has no
effect in 'global-auto-revert-mode'. The default value is nil.
* New Modes and Packages in Emacs 27.1

View file

@ -107,7 +107,7 @@
;; Dependencies:
(eval-when-compile (require 'cl-lib))
(require 'cl-lib)
(require 'timer)
(require 'filenotify)
@ -302,6 +302,29 @@ You should set this variable through Custom."
:type 'regexp
:version "24.4")
(defcustom auto-revert-avoid-polling nil
"Non-nil to avoid polling files when notification is available.
Set this variable to a non-nil value to save power by avoiding
polling when possible. Files on file-systems that do not support
change notifications must match `auto-revert-notify-exclude-dir-regexp'
for Auto-Revert to work properly in this case. This typically
includes files on network file systems on Unix-like machines,
when those files are modified from another computer.
When nil, buffers in Auto-Revert Mode will always be polled for
changes to their files on disk every `auto-revert-interval'
seconds, in addition to using notification for those files.
In Global Auto-Revert Mode, polling is always done regardless of
the value of this variable."
:group 'auto-revert
:type 'boolean
:set (lambda (variable value)
(set-default variable value)
(auto-revert-set-timer))
:version "27.1")
;; Internal variables:
(defvar auto-revert-buffer-list ()
@ -479,9 +502,32 @@ specifies in the mode line."
(auto-revert-buffers)
(dolist (buf (buffer-list))
(with-current-buffer buf
(when auto-revert-notify-watch-descriptor
(when (and auto-revert-notify-watch-descriptor
(not (memq buf auto-revert-buffer-list)))
(auto-revert-notify-rm-watch))))))
(defun auto-revert--polled-buffers ()
"List of buffers that need to be polled."
(cond (global-auto-revert-mode (buffer-list))
(auto-revert-avoid-polling
(mapcan (lambda (buffer)
(and (not (buffer-local-value
'auto-revert-notify-watch-descriptor buffer))
(list buffer)))
auto-revert-buffer-list))
(t auto-revert-buffer-list)))
;; Same as above in a boolean context, but cheaper.
(defun auto-revert--need-polling-p ()
"Whether periodic polling is required."
(or global-auto-revert-mode
(if auto-revert-avoid-polling
(not (cl-every (lambda (buffer)
(buffer-local-value
'auto-revert-notify-watch-descriptor buffer))
auto-revert-buffer-list))
auto-revert-buffer-list)))
(defun auto-revert-set-timer ()
"Restart or cancel the timer used by Auto-Revert Mode.
If such a timer is active, cancel it. Start a new timer if
@ -492,10 +538,10 @@ will use an up-to-date value of `auto-revert-interval'"
(if (timerp auto-revert-timer)
(cancel-timer auto-revert-timer))
(setq auto-revert-timer
(if (or global-auto-revert-mode auto-revert-buffer-list)
(run-with-timer auto-revert-interval
auto-revert-interval
'auto-revert-buffers))))
(and (auto-revert--need-polling-p)
(run-with-timer auto-revert-interval
auto-revert-interval
'auto-revert-buffers))))
(defun auto-revert-notify-rm-watch ()
"Disable file notification for current buffer's associated file."
@ -558,24 +604,20 @@ will use an up-to-date value of `auto-revert-interval'"
;; often, we want to skip some revert operations so that we don't spend all our
;; time reverting the buffer.
;;
;; We do this by reverting immediately in response to the first in a flurry of
;; notifications. We suppress subsequent notifications until the next time
;; `auto-revert-buffers' is called (this happens on a timer with a period set by
;; `auto-revert-interval').
(defvar auto-revert-buffers-counter 1
"Incremented each time `auto-revert-buffers' is called")
(defvar-local auto-revert-buffers-counter-lockedout 0
"Buffer-local value to indicate whether we should immediately
update the buffer on a notification event or not. If
;; We do this by reverting immediately in response to the first in a
;; flurry of notifications. Any notifications during the following
;; `auto-revert-lockout-interval' seconds are noted but not acted upon
;; until the end of that interval.
(= auto-revert-buffers-counter-lockedout
auto-revert-buffers-counter)
(defconst auto-revert--lockout-interval 2.5
"Duration, in seconds, of the Auto-Revert Mode notification lockout.
This is the quiescence after each notification of a file being
changed during which no automatic reverting takes place, to
prevent many updates in rapid succession from overwhelming the
system.")
then the updates are locked out, and we wait until the next call
of `auto-revert-buffers' to revert the buffer. If no lockout is
present, then we revert immediately and set the lockout, so that
no more reverts are possible until the next call of
`auto-revert-buffers'")
(defvar-local auto-revert--lockout-timer nil
"Timer awaiting the end of the notification lockout interval, or nil.")
(defun auto-revert-notify-handler (event)
"Handle an EVENT returned from file notification."
@ -604,7 +646,11 @@ no more reverts are possible until the next call of
(file-name-nondirectory buffer-file-name)))
;; A buffer w/o a file, like dired.
(null buffer-file-name))
(auto-revert-notify-rm-watch))))
(auto-revert-notify-rm-watch)
;; Restart the timer if it wasn't running.
(when (and (memq buffer auto-revert-buffer-list)
(not auto-revert-timer))
(auto-revert-set-timer)))))
;; Loop over all buffers, in order to find the intended one.
(cl-dolist (buffer buffers)
@ -630,11 +676,21 @@ no more reverts are possible until the next call of
(setq auto-revert-notify-modified-p t)
;; Revert the buffer now if we're not locked out.
(when (/= auto-revert-buffers-counter-lockedout
auto-revert-buffers-counter)
(unless auto-revert--lockout-timer
(auto-revert-handler)
(setq auto-revert-buffers-counter-lockedout
auto-revert-buffers-counter))))))))))
(setq auto-revert--lockout-timer
(run-with-timer
auto-revert--lockout-interval nil
#'auto-revert--end-lockout buffer)))))))))))
(defun auto-revert--end-lockout (buffer)
"End the lockout period after a notification.
If the buffer needs to be reverted, do it now."
(when (buffer-live-p buffer)
(with-current-buffer buffer
(setq auto-revert--lockout-timer nil)
(when auto-revert-notify-modified-p
(auto-revert-handler)))))
(defun auto-revert-active-p ()
"Check if auto-revert is active (in current buffer or globally)."
@ -755,13 +811,8 @@ This function is also responsible for removing buffers no longer in
Auto-Revert Mode from `auto-revert-buffer-list', and for canceling
the timer when no buffers need to be checked."
(setq auto-revert-buffers-counter
(1+ auto-revert-buffers-counter))
(save-match-data
(let ((bufs (if global-auto-revert-mode
(buffer-list)
auto-revert-buffer-list))
(let ((bufs (auto-revert--polled-buffers))
remaining new)
;; Buffers with remote contents shall be reverted only if the
;; connection is established already.
@ -810,8 +861,7 @@ the timer when no buffers need to be checked."
(setq bufs (cdr bufs)))
(setq auto-revert-remaining-buffers bufs)
;; Check if we should cancel the timer.
(when (and (not global-auto-revert-mode)
(null auto-revert-buffer-list))
(unless (auto-revert--need-polling-p)
(if (timerp auto-revert-timer)
(cancel-timer auto-revert-timer))
(setq auto-revert-timer nil)))))