From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Phil Sainty Newsgroups: gmane.emacs.bugs Subject: bug#14820: 24.3; elisp manual: How to write good idle timer worker functions? Date: Tue, 09 Jul 2013 01:25:58 +1200 Message-ID: <51DABDE6.10904@orcon.net.nz> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit X-Trace: ger.gmane.org 1373290038 14022 80.91.229.3 (8 Jul 2013 13:27:18 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Mon, 8 Jul 2013 13:27:18 +0000 (UTC) To: 14820@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Mon Jul 08 15:27:17 2013 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1UwBTJ-0006L6-3n for geb-bug-gnu-emacs@m.gmane.org; Mon, 08 Jul 2013 15:27:17 +0200 Original-Received: from localhost ([::1]:59349 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UwBTI-00053h-Ly for geb-bug-gnu-emacs@m.gmane.org; Mon, 08 Jul 2013 09:27:16 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:42268) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UwBTC-00052N-Pz for bug-gnu-emacs@gnu.org; Mon, 08 Jul 2013 09:27:13 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1UwBT9-0002qc-UF for bug-gnu-emacs@gnu.org; Mon, 08 Jul 2013 09:27:10 -0400 Original-Received: from debbugs.gnu.org ([140.186.70.43]:44628) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UwBT9-0002qY-Ri for bug-gnu-emacs@gnu.org; Mon, 08 Jul 2013 09:27:07 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.80) (envelope-from ) id 1UwBT8-0001N1-V7 for bug-gnu-emacs@gnu.org; Mon, 08 Jul 2013 09:27:07 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Phil Sainty Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Mon, 08 Jul 2013 13:27:06 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 14820 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.13732899895160 (code B ref -1); Mon, 08 Jul 2013 13:27:06 +0000 Original-Received: (at submit) by debbugs.gnu.org; 8 Jul 2013 13:26:29 +0000 Original-Received: from localhost ([127.0.0.1]:38941 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1UwBSV-0001L3-5M for submit@debbugs.gnu.org; Mon, 08 Jul 2013 09:26:28 -0400 Original-Received: from eggs.gnu.org ([208.118.235.92]:40183) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1UwBSP-0001KS-LF for submit@debbugs.gnu.org; Mon, 08 Jul 2013 09:26:23 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1UwBSI-0002db-BS for submit@debbugs.gnu.org; Mon, 08 Jul 2013 09:26:16 -0400 Original-Received: from lists.gnu.org ([2001:4830:134:3::11]:58614) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UwBSI-0002dX-78 for submit@debbugs.gnu.org; Mon, 08 Jul 2013 09:26:14 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:41875) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UwBSF-0004vh-97 for bug-gnu-emacs@gnu.org; Mon, 08 Jul 2013 09:26:14 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1UwBSB-0002c5-7r for bug-gnu-emacs@gnu.org; Mon, 08 Jul 2013 09:26:11 -0400 Original-Received: from nctlincom02.orcon.net.nz ([60.234.4.76]:34403) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UwBSA-0002ZB-LU for bug-gnu-emacs@gnu.org; Mon, 08 Jul 2013 09:26:07 -0400 Original-Received: from mx9.orcon.net.nz (mx9.orcon.net.nz [219.88.242.59]) by nctlincom02.orcon.net.nz (8.14.3/8.14.3/Debian-9.4) with ESMTP id r68DPwbC010340 for ; Tue, 9 Jul 2013 01:25:58 +1200 Original-Received: from Debian-exim by mx9.orcon.net.nz with local (Exim 4.69) (envelope-from ) id 1UwBS1-00044h-W6 for bug-gnu-emacs@gnu.org; Tue, 09 Jul 2013 01:25:58 +1200 Original-Received: from [121.99.89.166] (helo=[10.1.1.3]) by mx9.orcon.net.nz with esmtpa (Exim 4.69) (envelope-from ) id 1UwBS1-00044Y-QD for bug-gnu-emacs@gnu.org; Tue, 09 Jul 2013 01:25:57 +1200 User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:17.0) Gecko/20130620 Thunderbird/17.0.7 X-DSPAM-Check: by mx9.orcon.net.nz on Tue, 09 Jul 2013 01:25:57 +1200 X-DSPAM-Result: Innocent X-DSPAM-Processed: Tue Jul 9 01:25:57 2013 X-DSPAM-Confidence: 0.5922 X-DSPAM-Probability: 0.0000 X-Bayes-Prob: 0.5 (Score 0, tokens from: @@RPTN, default) X-CanIt-Geo: ip=121.99.89.166; country=NZ; region=E7; city=Auckland; latitude=-36.8667; longitude=174.7667; http://maps.google.com/maps?q=-36.8667,174.7667&z=6 X-CanItPRO-Stream: base:default X-Canit-Stats-ID: 05JVNpWqS - 0b1c70b700d8 - 20130709 X-Scanned-By: CanIt (www . roaringpenguin . com) on 172.16.100.175 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.15 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 140.186.70.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.bugs:76058 Archived-At: Documentation and/or possible enhancement needed. Sorry, this is a wee bit long; the short version is that I'd like to see the elisp manual provide a canonical solution to the problems raised (but not resolved) in (elisp) Idle Timers in the paragraph beginning "Do not write an idle timer function containing a loop"... The long version is: I'm trying to do some work with idle timers, but this seems like one of the trickier aspects of Emacs to work with, and I'm finding it hard to determine the correct approach. In particular I'm wishing that the documentation at "(elisp) Idle Timers" provided some robust example code which could safely be used as a template for this kind of thing. I feel that improved documentation would probably help a lot of people. (I would be happy to draft up some changes to the documentation myself if only I knew for sure what that example code should look like.) Frustratingly, the documentation actually tells me what NOT to do, but does not tell me what I should do instead. Here's the use case: There is a substantial amount of work to perform 'in the background', taking (in total) in excess of 10 seconds to complete, and also needing to be run on a semi-regular basis, so making the user wait each time is out of the question. I wish to perform the work in idle time, as quickly as possible, but without disrupting the user. The work can be broken down into a queue of small items, each of which takes a fraction of a second to complete. When my idle timer triggers, I would like Emacs to start processing the queue, and continue to process the queue without pausing unless Emacs ceases to be idle (or should be dealing with something else, at any rate). It's this last part where I've been struggling with the documentation. Essentially I want to iterate over the queue, and after processing each item I want to check to see if Emacs is waiting to do something else. If not I would like it to continue processing immediately, not introducing any unnecessary pauses. If there is some kind of input or other necessary activity pending, I need to break out of my loop and wait for Emacs to become idle again before resuming. At present the documentation says: > Do not write an idle timer function containing a loop which does a > certain amount of processing each time around, and exits when > `(input-pending-p)' is non-`nil'. This approach seems very natural > but has two problems: > > * It blocks out all process output (since Emacs accepts process > output only while waiting). > > * It blocks out any idle timers that ought to run during that time. So that helpfully warns me about the wrong approach for my use case, but doesn't say what I should do instead (the remainder of that info node discusses intentionally introducing a delay into the processing, which seems like a different topic -- I don't wish to introduce any delays if I can avoid it). The quote above indicates that `input-pending-p' is not sufficient to detect all kinds of pending activity, but it doesn't suggest other options. My current impression is that the `sit-for' function seems like the way to allow Emacs check for other pending activity, and that (sit-for 0 t) looks like a way to do so without adding unnecessary delays or activity. I've tried this in conjunction with `with-timeout' and it does enable me to make Emacs break out of a loop after a certain duration, which suggests to me that this resolves the first of the documentation's stated problems -- blocking other idle timers (given that `with-timeout' depends upon timers). So with the `sit-for' call added into the mix, is it then enough to check `input-pending-p'? Will this combination also deal with the second stated problem of blocking process output? Or is this still insufficient in some cases? In other words, will something like the following be a robust approach in general? (defvar my-queue nil "Queue of items to process") (defvar my-idle-duration 2 "Required idle time before starting") (defvar my-timer nil) (defvar my-resume-timer nil) ;; after populating the queue, initialise processing with: (setq my-timer (run-with-idle-timer my-idle-duration t 'my-process-queue)) (defun my-process-queue () "Process the queue until interrupted." (while (and my-queue (not (input-pending-p))) (my-process-queue-item (pop my-queue)) (sit-for 0 t)) (if my-queue (let ((idle (current-idle-time))) (when idle ;; Schedule a one-off resume timer. (when (timerp my-resume-timer) (cancel-timer my-resume-timer)) (setq my-resume-timer (run-with-idle-timer (time-add idle (seconds-to-time my-idle-duration)) nil 'my-process-queue)))) ;; Queue is empty; cancel the repeating timer. (cancel-timer my-timer) (when (timerp my-resume-timer) (cancel-timer my-resume-timer)))) I also don't know if the sit-for duration of zero would prevent Emacs from doing things that a larger number would enable? And if so, what is the best way to ensure that Emacs will be able to deal with any other processes, while not also causing undue delays in processing the queue items? Basically I don't know enough about how Emacs interacts with processes to be able to answer these kinds of questions myself, and it would be great if the manual provided enough detail that I could be confident about making Emacs perform arbitrary work in idle time without introducing any potential problems. Perhaps there's a good case to be made for introducing some new macro into the API to provide a simple standard way of implementing this kind of idle-time worker function? There's certainly enough boiler-plate in the code I've come up with that some kind of wrapper would seem warranted, and I would suspect that this is true even if I'm barking up the wrong tree with my approach. thanks, -Phil