* using finalizers @ 2021-12-30 22:43 Tomas Hlavaty 2021-12-31 2:46 ` LdBeth 2021-12-31 14:39 ` LdBeth 0 siblings, 2 replies; 42+ messages in thread From: Tomas Hlavaty @ 2021-12-30 22:43 UTC (permalink / raw) To: emacs-devel Hi, I am trying to understand finalizers in Emacs Lisp but I am unable to triger it. Here is an example (with lexical-binding): (defun finito () (let ((x '(a b c))) (letrec ((more (make-finalizer close)) (close (lambda () (print "close-resource") (setq more nil)))) (lambda () (when more (pop x)))))) (setq foo (finito)) (equal '(a b c nil nil) (mapcar (lambda (x) (funcall foo)) '(1 2 3 4 5))) (setq foo nil) (garbage-collect) After garbage collection, I would expect to see close-resource text in the *Messages* buffer but there is none. The interface is different from Common Lisp, where make-finalizer usually expects object and thunk, not thunk only but I guess the problem lies somewhere else. What is the expected example usage of finalizers in Emacs Lisp? How can I verify, that my finalizer works? Thanks in advance Tomas ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-30 22:43 using finalizers Tomas Hlavaty @ 2021-12-31 2:46 ` LdBeth 2021-12-31 3:29 ` Stefan Monnier ` (2 more replies) 2021-12-31 14:39 ` LdBeth 1 sibling, 3 replies; 42+ messages in thread From: LdBeth @ 2021-12-31 2:46 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: emacs-devel >>>>> In <878rw1pvcw.fsf@logand.com> >>>>> Tomas Hlavaty <tom@logand.com> wrote: > Hi, > I am trying to understand finalizers in Emacs Lisp but I am unable to > triger it. > Here is an example (with lexical-binding): > (defun finito () > (let ((x '(a b c))) > (letrec ((more (make-finalizer close)) > (close (lambda () > (print "close-resource") > (setq more nil)))) > (lambda () > (when more > (pop x)))))) > (setq foo (finito)) > (equal '(a b c nil nil) (mapcar (lambda (x) (funcall foo)) '(1 2 3 4 5))) > (setq foo nil) > (garbage-collect) > After garbage collection, I would expect to see close-resource text in > the *Messages* buffer but there is none. You are probably taking the wrong assumption about the effect of letrec (macroexpand '(letrec ((more (make-finalizer close)) (close (lambda () (setq bar t)))) (lambda () (when more (pop x))))) ==> (let (more close) (setq more (make-finalizer close)) (setq close (lambda nil (setq bar t))) (lambda nil (when more (pop x)))) So, `close' is nil and the lambda is never set as the finalizer. I think Common Lisp doesn't have letrec in the standard. And you are proabaly taking the assumption that ELisp's letrec works as well as Scheme's one. But it is not. > What is the expected example usage of finalizers in Emacs Lisp? > How can I verify, that my finalizer works? (setq bar nil) (setq foo (make-finalizer (lambda () (setq bar t)))) (setq foo nil) (garbage-collect) bar ; => t -- LDB ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 2:46 ` LdBeth @ 2021-12-31 3:29 ` Stefan Monnier 2021-12-31 3:36 ` Stefan Monnier 2021-12-31 4:30 ` LdBeth 2021-12-31 6:28 ` Tomas Hlavaty 2021-12-31 7:50 ` Eli Zaretskii 2 siblings, 2 replies; 42+ messages in thread From: Stefan Monnier @ 2021-12-31 3:29 UTC (permalink / raw) To: LdBeth; +Cc: Tomas Hlavaty, emacs-devel > You are probably taking the wrong assumption about the effect of letrec > > (macroexpand '(letrec ((more (make-finalizer close)) > (close (lambda () > (setq bar t)))) > (lambda () > (when more > (pop x))))) > > ==> (let (more close) > (setq more (make-finalizer close)) > (setq close (lambda nil (setq bar t))) > (lambda nil (when more (pop x)))) Indeed, the code needs to swap `more` and `close`. > I think Common Lisp doesn't have letrec in the standard. And you are > proabaly taking the assumption that ELisp's letrec works as well as > Scheme's one. But it is not. I don't think Scheme's `letrec` works any differently in this respect, except that instead of nil, variables get a special "void like" value before they're initialized. In the above code, the real bug I can see which we should fix is that `make-finalizer` should signal an error if its arg is not a function. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 3:29 ` Stefan Monnier @ 2021-12-31 3:36 ` Stefan Monnier 2021-12-31 4:30 ` LdBeth 1 sibling, 0 replies; 42+ messages in thread From: Stefan Monnier @ 2021-12-31 3:36 UTC (permalink / raw) To: LdBeth; +Cc: Tomas Hlavaty, emacs-devel >> (macroexpand '(letrec ((more (make-finalizer close)) >> (close (lambda () >> (setq bar t)))) >> (lambda () >> (when more >> (pop x))))) [...] > Indeed, the code needs to swap `more` and `close`. Actually, simpler: (letrec ((more (make-finalizer (lambda () (print "close-resource") (setq more nil))))) (lambda () (when more (pop x))))) -- Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 3:29 ` Stefan Monnier 2021-12-31 3:36 ` Stefan Monnier @ 2021-12-31 4:30 ` LdBeth 2021-12-31 4:43 ` LdBeth 1 sibling, 1 reply; 42+ messages in thread From: LdBeth @ 2021-12-31 4:30 UTC (permalink / raw) To: Stefan Monnier; +Cc: LdBeth, Tomas Hlavaty, emacs-devel >>>>> In <jwv35m94fqf.fsf-monnier+emacs@gnu.org> >>>>> Stefan Monnier <monnier@iro.umontreal.ca> wrote: Stefan> I don't think Scheme's `letrec` works any differently in this respect, Stefan> except that instead of nil, variables get a special "void like" value Stefan> before they're initialized. Right. With that "void" like value, a sane Scheme would probably signal error at runtime. The simple letrec subr.el macro does not do that check. Stefan> In the above code, the real bug I can see which we should fix is that Stefan> `make-finalizer` should signal an error if its arg is not a function. There's nothing wrong with the behavior of `make-finalizer`, it just treats a symbol as a function specifier, just like elisp does not doing anything to prevent a user from `fset' symbol `nil' and `(funcall nil)'. -- LDB ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 4:30 ` LdBeth @ 2021-12-31 4:43 ` LdBeth 0 siblings, 0 replies; 42+ messages in thread From: LdBeth @ 2021-12-31 4:43 UTC (permalink / raw) To: Stefan Monnier; +Cc: LdBeth, Tomas Hlavaty, emacs-devel >>>>> In <tencent_4CC28DA9FBCB31B64701835E87D4CAFF4F05@qq.com> >>>>> LdBeth <andpuke@foxmail.com> wrote: >>>>> In <jwv35m94fqf.fsf-monnier+emacs@gnu.org> >>>>> Stefan Monnier <monnier@iro.umontreal.ca> wrote: ldb> There's nothing wrong with the behavior of `make-finalizer`, it just ldb> treats a symbol as a function specifier, just like elisp does not ldb> doing anything to prevent a user from `fset' symbol `nil' and ldb> `(funcall nil)'. Allow me to make a correction: Emacs 27 would report a error when `(fset nil (lambda () 1))', but some Common Lisps would allow that, and `(fset t (lambda () 1))' works in Elisp. I guess it's because of the difference in some implementation details. While `(symbolp nil) => t', `nil' is actually a constant stands for "empty list" that doesn't have the corresponding definition slots. But still, I think the behavior of `make-finalizer' doesn't need a fix. -- LDB ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 2:46 ` LdBeth 2021-12-31 3:29 ` Stefan Monnier @ 2021-12-31 6:28 ` Tomas Hlavaty 2021-12-31 10:59 ` LdBeth 2022-01-01 17:00 ` Stefan Monnier 2021-12-31 7:50 ` Eli Zaretskii 2 siblings, 2 replies; 42+ messages in thread From: Tomas Hlavaty @ 2021-12-31 6:28 UTC (permalink / raw) To: LdBeth; +Cc: emacs-devel On Fri 31 Dec 2021 at 10:46, LdBeth <andpuke@foxmail.com> wrote: >>>>>> In <878rw1pvcw.fsf@logand.com> Tomas Hlavaty <tom@logand.com> wrote: > You are probably taking the wrong assumption about the effect of letrec > So, `close' is nil and the lambda is never set as the finalizer. You are right, the order was wrong. >> What is the expected example usage of finalizers in Emacs Lisp? >> How can I verify, that my finalizer works? > > (setq bar nil) > (setq foo (make-finalizer (lambda () (setq bar t)))) > (setq foo nil) > (garbage-collect) > bar ; => t Nice, that works. One more question: Because make-finalizer in Emacs Lisp does not take the object into account, I have to make sure to hold the reference to the finalizer somehow. However, this might not be trivial, because a smart compiler could eliminate the reference. Is this not an issue? Shouldn't make-finalizer take the object as an argument and not rely on me holding the reference explicitly? Thank you! ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 6:28 ` Tomas Hlavaty @ 2021-12-31 10:59 ` LdBeth 2021-12-31 11:18 ` Tomas Hlavaty 2022-01-01 17:00 ` Stefan Monnier 1 sibling, 1 reply; 42+ messages in thread From: LdBeth @ 2021-12-31 10:59 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: LdBeth, emacs-devel >>>>> In <875yr5p9t3.fsf@logand.com> >>>>> Tomas Hlavaty <tom@logand.com> wrote: Tomas> One more question: Because make-finalizer in Emacs Lisp does Tomas> not take the object into account, I have to make sure to hold Tomas> the reference to the finalizer somehow. However, this might Tomas> not be trivial, because a smart compiler could eliminate the Tomas> reference. Is this not an issue? That's being too smart. And I don't think the bytecode compiler can do the lifetime analaysis right now. It is possible that this could be changed in future releases of Emacs. Tomas> Shouldn't make-finalizer take the object as an argument and not Tomas> rely on me holding the reference explicitly? You would only need finalizers for complex objects, so ideally you could use OOP to manage that, probably EIEIO but not limited to that, and you may even come up with your own. -- LDB ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 10:59 ` LdBeth @ 2021-12-31 11:18 ` Tomas Hlavaty 2021-12-31 11:41 ` LdBeth 0 siblings, 1 reply; 42+ messages in thread From: Tomas Hlavaty @ 2021-12-31 11:18 UTC (permalink / raw) To: LdBeth; +Cc: emacs-devel On Fri 31 Dec 2021 at 18:59, LdBeth <andpuke@foxmail.com> wrote: >>>>>> In <875yr5p9t3.fsf@logand.com> Tomas Hlavaty <tom@logand.com> wrote: > Tomas> One more question: Because make-finalizer in Emacs Lisp does > Tomas> not take the object into account, I have to make sure to hold > Tomas> the reference to the finalizer somehow. However, this might > Tomas> not be trivial, because a smart compiler could eliminate the > Tomas> reference. Is this not an issue? > > That's being too smart. And I don't think the bytecode compiler can do > the lifetime analaysis right now. It is possible that this could be > changed in future releases of Emacs. ok, so if I understand it correctly, in other words, the make-finalizer interface relies on the compiler being not very smart > Tomas> Shouldn't make-finalizer take the object as an argument and not > Tomas> rely on me holding the reference explicitly? > > You would only need finalizers for complex objects, so ideally you > could use OOP to manage that, probably EIEIO but not limited to that, > and you may even come up with your own. What do you mean? Why not do it "properly" and future-proof the make-finalizer interface, pass the object explicitly there and leave the rest as implementation detail instead of leaking the issue to the programmer? ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 11:18 ` Tomas Hlavaty @ 2021-12-31 11:41 ` LdBeth 2021-12-31 12:01 ` Tomas Hlavaty 0 siblings, 1 reply; 42+ messages in thread From: LdBeth @ 2021-12-31 11:41 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: LdBeth, emacs-devel >>>>> In <87bl0xm39e.fsf@logand.com> >>>>> Tomas Hlavaty <tom@logand.com> wrote: ldb> You would only need finalizers for complex objects, so ideally you ldb> could use OOP to manage that, probably EIEIO but not limited to that, ldb> and you may even come up with your own. Tomas> What do you mean? Tomas> Why not do it "properly" and future-proof the make-finalizer interface, Tomas> pass the object explicitly there and leave the rest as implementation Tomas> detail instead of leaking the issue to the programmer? Isn't it doesn't make sense to associate a primitive data types such as numbers, symbols, strings with a finalizer? If you need such a feature on primitive data types, you are probably using this in a way not intended. And meawhile it would be problematic to hardcode what kinds of objects are allowed to have objects. Notice this is a C function so it is not very flexible. You can think this is the primitive API that Emacs provides, and people can build up more higher level ones that suit their particular needs. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 11:41 ` LdBeth @ 2021-12-31 12:01 ` Tomas Hlavaty 0 siblings, 0 replies; 42+ messages in thread From: Tomas Hlavaty @ 2021-12-31 12:01 UTC (permalink / raw) To: LdBeth; +Cc: emacs-devel On Fri 31 Dec 2021 at 19:41, LdBeth <andpuke@foxmail.com> wrote: >>>>>> In <87bl0xm39e.fsf@logand.com> >>>>>> Tomas Hlavaty <tom@logand.com> wrote: > > ldb> You would only need finalizers for complex objects, so ideally you > ldb> could use OOP to manage that, probably EIEIO but not limited to that, > ldb> and you may even come up with your own. > > Tomas> What do you mean? > > Tomas> Why not do it "properly" and future-proof the make-finalizer interface, > Tomas> pass the object explicitly there and leave the rest as implementation > Tomas> detail instead of leaking the issue to the programmer? > > Isn't it doesn't make sense to associate a primitive data types such > as numbers, symbols, strings with a finalizer? If you need such a > feature on primitive data types, you are probably using this in a way > not intended. > > And meawhile it would be problematic to hardcode what kinds of objects > are allowed to have objects. Notice this is a C function so it is not > very flexible. This is irrelevant, I am not talking about that. > You can think this is the primitive API that Emacs provides, and > people can build up more higher level ones that suit their particular > needs. This primitive API that Emacs provides relies on the implicit assumption of the compiler being not smart. I do not see how that is a good idea. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 6:28 ` Tomas Hlavaty 2021-12-31 10:59 ` LdBeth @ 2022-01-01 17:00 ` Stefan Monnier 2022-01-01 20:25 ` Tomas Hlavaty 1 sibling, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2022-01-01 17:00 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: LdBeth, emacs-devel > One more question: Because make-finalizer in Emacs Lisp does not take > the object into account, Not sure what you mean by "the object" here. > I have to make sure to hold the reference to the finalizer somehow. > However, this might not be trivial, because a smart compiler could > eliminate the reference. And I definitely don't know what you mean by "the reference", so I have no idea why a smart compiler would be able to eliminate it. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 17:00 ` Stefan Monnier @ 2022-01-01 20:25 ` Tomas Hlavaty 2022-01-01 20:47 ` Stefan Monnier 0 siblings, 1 reply; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-01 20:25 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel On Sat 01 Jan 2022 at 12:00, Stefan Monnier <monnier@iro.umontreal.ca> wrote: >> One more question: Because make-finalizer in Emacs Lisp does not take >> the object into account, > > Not sure what you mean by "the object" here. In Common Lisp, the usual semantics is: call this thunk when this object gets collected. I pass the object and thunk and make-finalizer does the rest. In Emacs Lisp, the semantics is: call this thunk when the returned finalizer gets collected. So, if I want to finalize my object, I have too keep the reference to the finalizer. >> I have to make sure to hold the reference to the finalizer somehow. >> However, this might not be trivial, because a smart compiler could >> eliminate the reference. > > And I definitely don't know what you mean by "the reference", so I have > no idea why a smart compiler would be able to eliminate it. In Emacs Lisp, my object itself needs to keep the finalizer around, otherwise it would get finalized. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 20:25 ` Tomas Hlavaty @ 2022-01-01 20:47 ` Stefan Monnier 2022-01-01 22:55 ` Tomas Hlavaty 0 siblings, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2022-01-01 20:47 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: LdBeth, emacs-devel > In Emacs Lisp, the semantics is: call this thunk when the returned > finalizer gets collected. So, if I want to finalize my object, I have > too keep the reference to the finalizer. That's right: if you want an object to have a finalizer, you need to save that finalizer somewhere inside the object. IOW the object literally "has a finalizer" somewhere inside. There's no doubt that it's different. Do you have concrete cases where this difference introduces a difficulty? Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 20:47 ` Stefan Monnier @ 2022-01-01 22:55 ` Tomas Hlavaty 2022-01-01 23:18 ` Stefan Monnier 2022-01-01 23:26 ` Tomas Hlavaty 0 siblings, 2 replies; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-01 22:55 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel On Sat 01 Jan 2022 at 15:47, Stefan Monnier <monnier@iro.umontreal.ca> wrote: > That's right: if you want an object to have a finalizer, you need to > save that finalizer somewhere inside the object. > IOW the object literally "has a finalizer" somewhere inside. > > There's no doubt that it's different. > Do you have concrete cases where this difference introduces a difficulty? In my original example: (defun finito () (let ((x '(a b c))) (letrec ((more (make-finalizer close)) (close (lambda () (print "close-resource") (setq more nil)))) (lambda () (when more (pop x)))))) I use a closure as my object that should have the finalizer "attached" to. In order to keep the reference to the finalizer, I have to use it inside the closure for something (here I stuffed it into the variable called more). That is inconvenient. Second issue is that an advanced compiler could eliminate even my attempt at using the finalizer for something if it determines that the finalizer is not actually used for something. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 22:55 ` Tomas Hlavaty @ 2022-01-01 23:18 ` Stefan Monnier 2022-01-01 23:47 ` Tomas Hlavaty 2022-01-01 23:26 ` Tomas Hlavaty 1 sibling, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2022-01-01 23:18 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: LdBeth, emacs-devel > > Do you have concrete cases where this difference introduces a difficulty? > In my original example: I miswrote, by "concrete" I meant a real-word use, no a made-up case. [ But at least, now I understand what you meant by the fact that the behavior depends on the byte-compiler's analysis. ] In any case, I don't think we have much experience with the current `make-finalizer` API (nor with other finalizer APIs) in ELisp, so I think there's room for tweaks/changes. But it should be based on concrete cases in real world ELisp code. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 23:18 ` Stefan Monnier @ 2022-01-01 23:47 ` Tomas Hlavaty 0 siblings, 0 replies; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-01 23:47 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel On Sat 01 Jan 2022 at 18:18, Stefan Monnier <monnier@iro.umontreal.ca> wrote: >> > Do you have concrete cases where this difference introduces a >> > difficulty? Well, maybe not difficulty but inconvenience and potential future difficulty. I found make-finalizer surprising and inconvenient. But LdBeth pointed out some interesting things I need to read about. > I miswrote, by "concrete" I meant a real-word use, no a made-up case. It was a reduced case of: (defun directory-brook (directory) (let (d) (letrec ((close (lambda () (when d (close-directory d) (setq d nil more nil)))) (more (make-finalizer close))) (lambda () (when more (unless d (setq d (open-directory directory))) (let (z (again t)) (while again (setq z (read-directory d)) (cond ((equal z ".")) ((equal z "..")) (z (setq again nil)) (t (setq again nil) (funcall close)))) z)))))) There are bits missing but it should give you an idea. > [ But at least, now I understand what you meant by the fact that the > behavior depends on the byte-compiler's analysis. ] Great, that was difficult for me to express. > In any case, I don't think we have much experience with the current > `make-finalizer` API (nor with other finalizer APIs) in ELisp, so > I think there's room for tweaks/changes. But it should be based > on concrete cases in real world ELisp code. Understand, thank you. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 22:55 ` Tomas Hlavaty 2022-01-01 23:18 ` Stefan Monnier @ 2022-01-01 23:26 ` Tomas Hlavaty 1 sibling, 0 replies; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-01 23:26 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel On Sat 01 Jan 2022 at 23:55, Tomas Hlavaty <tom@logand.com> wrote: > On Sat 01 Jan 2022 at 15:47, Stefan Monnier <monnier@iro.umontreal.ca> wrote: >> That's right: if you want an object to have a finalizer, you need to >> save that finalizer somewhere inside the object. >> IOW the object literally "has a finalizer" somewhere inside. >> >> There's no doubt that it's different. >> Do you have concrete cases where this difference introduces a difficulty? > > In my original example: > > (defun finito () > (let ((x '(a b c))) > (letrec ((more (make-finalizer close)) > (close (lambda () > (print "close-resource") > (setq more nil)))) > (lambda () > (when more > (pop x)))))) > > I use a closure as my object that should have the finalizer "attached" > to. In order to keep the reference to the finalizer, I have to use it > inside the closure for something (here I stuffed it into the variable > called more). That is inconvenient. Second issue is that an advanced > compiler could eliminate even my attempt at using the finalizer for > something if it determines that the finalizer is not actually used for > something. For comparison in Common Lisp, my object would not need to reference the finalizer. Something like this: (defun finito () (let* ((x '(a b c)) (close (lambda () (print "close-resource"))) (object (lambda () (pop x)))) (finalize object close) object)) ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 2:46 ` LdBeth 2021-12-31 3:29 ` Stefan Monnier 2021-12-31 6:28 ` Tomas Hlavaty @ 2021-12-31 7:50 ` Eli Zaretskii 2021-12-31 9:31 ` Tomas Hlavaty 2 siblings, 1 reply; 42+ messages in thread From: Eli Zaretskii @ 2021-12-31 7:50 UTC (permalink / raw) To: LdBeth; +Cc: tom, emacs-devel > Date: Fri, 31 Dec 2021 10:46:20 +0800 > From: LdBeth <andpuke@foxmail.com> > Cc: emacs-devel@gnu.org > > > What is the expected example usage of finalizers in Emacs Lisp? > > How can I verify, that my finalizer works? > > (setq bar nil) > (setq foo (make-finalizer (lambda () > (setq bar t)))) > > (setq foo nil) > (garbage-collect) > > bar ; => t I think that the assumption that a call to garbage-collect will necessarily call the finalizer is also problematic: there's usually no simple way to make sure there are no references left to the object, and GC could have its own ideas when to actually garbage-collect an object. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 7:50 ` Eli Zaretskii @ 2021-12-31 9:31 ` Tomas Hlavaty 2021-12-31 12:27 ` Eli Zaretskii 2021-12-31 14:23 ` Rudolf Schlatte 0 siblings, 2 replies; 42+ messages in thread From: Tomas Hlavaty @ 2021-12-31 9:31 UTC (permalink / raw) To: Eli Zaretskii, LdBeth; +Cc: emacs-devel On Fri 31 Dec 2021 at 09:50, Eli Zaretskii <eliz@gnu.org> wrote: > I think that the assumption that a call to garbage-collect will > necessarily call the finalizer is also problematic: there's usually no > simple way to make sure there are no references left to the object, This breaks my expectations. Could you explain that more? I can see these cases where garbage collection might not do its job: 1. imprecise gc 2. program exit or abort 3. a leak to be fixed What do you have in mind, that could hold extra unexpected references and not be a leak? Does it mean that for example thunk-delay leaks even though it releases the reference to the body thunk? Does it mean that setting reference to nil in order to dispose an object (as in my original example) is futile? > and GC could have its own ideas when to actually garbage-collect an > object. I don't think timing is an issue. I think the expectation from gc is that it collects the object "eventually as needed". In the case of imprecise gc "hopefully eventually as needed". ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 9:31 ` Tomas Hlavaty @ 2021-12-31 12:27 ` Eli Zaretskii 2022-01-01 17:58 ` Tomas Hlavaty 2021-12-31 14:23 ` Rudolf Schlatte 1 sibling, 1 reply; 42+ messages in thread From: Eli Zaretskii @ 2021-12-31 12:27 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: emacs-devel > From: Tomas Hlavaty <tom@logand.com> > Cc: emacs-devel@gnu.org > Date: Fri, 31 Dec 2021 10:31:02 +0100 > > On Fri 31 Dec 2021 at 09:50, Eli Zaretskii <eliz@gnu.org> wrote: > > I think that the assumption that a call to garbage-collect will > > necessarily call the finalizer is also problematic: there's usually no > > simple way to make sure there are no references left to the object, > > This breaks my expectations. Could you explain that more? The main aspect to have in mind is that your Lisp code has no direct control on when a given Lisp object is no longer referenced by any other live object. And even if you take care to do that on the Lisp level (something that is notoriously hard, if possible), GC also scans the C stack for anything that looks like a reference to a Lisp object, and marks any such objects as not (yet) collectible. And your Lisp program has no control on what's on the C stack. > Does it mean that for example thunk-delay leaks even though it releases > the reference to the body thunk? If GC doesn't collect an object right away, it doesn't yet mean we have a leak. As long as the object is collected "eventually", we are fine. > Does it mean that setting reference to nil in order to dispose an object > (as in my original example) is futile? It doesn't necessarily guarantee GC will collect the object, yes. > > and GC could have its own ideas when to actually garbage-collect an > > object. > > I don't think timing is an issue. I think the expectation from gc is > that it collects the object "eventually as needed". That does happen, but you seem to expect more: you expect that the object is collected as soon as you do something in Lisp to break the binding between the object and the variable that was bound to it. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 12:27 ` Eli Zaretskii @ 2022-01-01 17:58 ` Tomas Hlavaty 2022-01-01 18:20 ` Eli Zaretskii 0 siblings, 1 reply; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-01 17:58 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel On Fri 31 Dec 2021 at 14:27, Eli Zaretskii <eliz@gnu.org> wrote: > That does happen, but you seem to expect more: you expect that the > object is collected as soon as you do something in Lisp to break the > binding between the object and the variable that was bound to it. No, that's not what I expect. I even said that. What I meant is that I did not expect some other code not under my control to keep references to the object forever preventing gc to run the finalizer eventually, which is how I understood your reply. I know at least one Lisp which does keeps extra unexpected references to particular kind of objects preventing finalizers to run, so that kind of behaviour would not be unheard of, but I see it as a bug and hope that Emacs does not have that issue. On another note, I see that finalizers are rarely used and tested: ./test/src/alloc-tests.el33: (should (equal (type-of (make-finalizer nil)) 'finalizer))) ./lisp/emacs-lisp/generator.el642: (make-finalizer For example the cps-generate-evaluator function has 70 lines and it is not obvious to me, that the use of finalizer is not buggy. Do you have an advice, how are programmers supposed to test finalizers? My original issue did not run the finalizer because of a bug in my use of letrec. Unless there is a good way to actually trigger the finalizer, there is no good way to test such code paths. Or shall the programmers just hope for the best? ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 17:58 ` Tomas Hlavaty @ 2022-01-01 18:20 ` Eli Zaretskii 2022-01-01 20:55 ` Stefan Monnier 0 siblings, 1 reply; 42+ messages in thread From: Eli Zaretskii @ 2022-01-01 18:20 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: emacs-devel > From: Tomas Hlavaty <tom@logand.com> > Cc: andpuke@foxmail.com, emacs-devel@gnu.org > Date: Sat, 01 Jan 2022 18:58:01 +0100 > > On Fri 31 Dec 2021 at 14:27, Eli Zaretskii <eliz@gnu.org> wrote: > > That does happen, but you seem to expect more: you expect that the > > object is collected as soon as you do something in Lisp to break the > > binding between the object and the variable that was bound to it. > > No, that's not what I expect. I even said that. What I meant is that I > did not expect some other code not under my control to keep references > to the object forever preventing gc to run the finalizer eventually, > which is how I understood your reply. Not "forever", of course, but depending on the program a reference to an object could live for quite some time. Having a reference on the C stack is the most sinister of these. > On another note, I see that finalizers are rarely used and tested: > > ./test/src/alloc-tests.el33: (should (equal (type-of (make-finalizer nil)) 'finalizer))) > ./lisp/emacs-lisp/generator.el642: (make-finalizer > > For example the cps-generate-evaluator function has 70 lines and it is > not obvious to me, that the use of finalizer is not buggy. > > Do you have an advice, how are programmers supposed to test finalizers? > > My original issue did not run the finalizer because of a bug in my use > of letrec. Unless there is a good way to actually trigger the > finalizer, there is no good way to test such code paths. Or shall the > programmers just hope for the best? I hope Stefan could answer that, but it sounds like he doesn't understand the issue you are describing (and neither do I, frankly). ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 18:20 ` Eli Zaretskii @ 2022-01-01 20:55 ` Stefan Monnier 2022-01-01 23:05 ` Tomas Hlavaty 0 siblings, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2022-01-01 20:55 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Tomas Hlavaty, emacs-devel >> No, that's not what I expect. I even said that. What I meant is that I >> did not expect some other code not under my control to keep references >> to the object forever preventing gc to run the finalizer eventually, >> which is how I understood your reply. > > Not "forever", of course, but depending on the program a reference to > an object could live for quite some time. Having a reference on the C > stack is the most sinister of these. A common problem during manual experimentation is to have the offending value stashed in `values`. >> My original issue did not run the finalizer because of a bug in my use >> of letrec. Unless there is a good way to actually trigger the >> finalizer, there is no good way to test such code paths. Or shall the >> programmers just hope for the best? Calling `garbage-collect` should usually work well enough for tests (tho this may have to be tweaked if/when we get a fancier GC), tho there's always the risk that some bitpattern somewhere inside the stack happens to look like a pointer to your finalizer (or an object that transitively references it), so no I don't know of a way to *reliably* get a finalizer to run. You can minimize the risk of a bitpattern getting in the way by trying to trigger a GC at a point where the C stack is as small as possible, but it still can't rule out the problem completely. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 20:55 ` Stefan Monnier @ 2022-01-01 23:05 ` Tomas Hlavaty 2022-01-01 23:21 ` Stefan Monnier 0 siblings, 1 reply; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-01 23:05 UTC (permalink / raw) To: Stefan Monnier, Eli Zaretskii; +Cc: emacs-devel On Sat 01 Jan 2022 at 15:55, Stefan Monnier <monnier@iro.umontreal.ca> wrote: >>> My original issue did not run the finalizer because of a bug in my use >>> of letrec. Unless there is a good way to actually trigger the >>> finalizer, there is no good way to test such code paths. Or shall the >>> programmers just hope for the best? > > Calling `garbage-collect` should usually work well enough for tests good, thanks > (tho this may have to be tweaked if/when we get a fancier GC), tho > there's always the risk that some bitpattern somewhere inside the stack > happens to look like a pointer to your finalizer (or an object that > transitively references it), so no I don't know of a way to *reliably* > get a finalizer to run. > > You can minimize the risk of a bitpattern getting in the way by trying > to trigger a GC at a point where the C stack is as small as possible, > but it still can't rule out the problem completely. What does "trigger a GC at a point where the C stack is as small as possible" mean? Does it mean trigger gc explicitly from top level? ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 23:05 ` Tomas Hlavaty @ 2022-01-01 23:21 ` Stefan Monnier 0 siblings, 0 replies; 42+ messages in thread From: Stefan Monnier @ 2022-01-01 23:21 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: Eli Zaretskii, emacs-devel > What does "trigger a GC at a point where the C stack is as small as > possible" mean? Does it mean trigger gc explicitly from top level? Something like that, yes. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 9:31 ` Tomas Hlavaty 2021-12-31 12:27 ` Eli Zaretskii @ 2021-12-31 14:23 ` Rudolf Schlatte 2021-12-31 16:21 ` Stefan Monnier 2022-01-01 22:36 ` Tomas Hlavaty 1 sibling, 2 replies; 42+ messages in thread From: Rudolf Schlatte @ 2021-12-31 14:23 UTC (permalink / raw) To: emacs-devel Tomas Hlavaty <tom@logand.com> writes: > > I can see these cases where garbage collection might not do its job: > > 1. imprecise gc > > 2. program exit or abort > > 3. a leak to be fixed 4. the gc is generational and the object is in an old generation 5. the gc is incremental and didn't get around to the object yet ... etc I'm sure you knew this already, but in general, using gc for non-memory resource management (e.g., "please close this file when this Lisp object is GCed") is not a good idea--depending on the GC behavior, you'll run out of file handles or whatnot. The RAII pattern in C++ deterministically calls a destructor when a stack-allocated object goes out of scope; in Lisp, the various `with-foo' macros serve the same purpose. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 14:23 ` Rudolf Schlatte @ 2021-12-31 16:21 ` Stefan Monnier 2022-01-01 17:37 ` Tomas Hlavaty 2022-01-01 22:36 ` Tomas Hlavaty 1 sibling, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2021-12-31 16:21 UTC (permalink / raw) To: Rudolf Schlatte; +Cc: emacs-devel Rudolf Schlatte [2021-12-31 15:23:32] wrote: > Tomas Hlavaty <tom@logand.com> writes: >> >> I can see these cases where garbage collection might not do its job: >> >> 1. imprecise gc >> >> 2. program exit or abort >> >> 3. a leak to be fixed > > 4. the gc is generational and the object is in an old generation > > 5. the gc is incremental and didn't get around to the object yet > > ... etc > > I'm sure you knew this already, but in general, using gc for non-memory > resource management (e.g., "please close this file when this Lisp object > is GCed") is not a good idea--depending on the GC behavior, you'll run > out of file handles or whatnot. The RAII pattern in C++ > deterministically calls a destructor when a stack-allocated object goes > out of scope; in Lisp, the various `with-foo' macros serve the same > purpose. But the context here is a "bug report" about a finalizer not being called and it seemed pretty clear that the call to `garbage-collect` was just there to try and make the bug more apparent, not because the real ELisp code relies on the finalizer being called right at the first call to `garbage-collect` after the object became unreachable. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 16:21 ` Stefan Monnier @ 2022-01-01 17:37 ` Tomas Hlavaty 0 siblings, 0 replies; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-01 17:37 UTC (permalink / raw) To: Stefan Monnier, Rudolf Schlatte; +Cc: emacs-devel On Fri 31 Dec 2021 at 11:21, Stefan Monnier <monnier@iro.umontreal.ca> wrote: > Rudolf Schlatte [2021-12-31 15:23:32] wrote: > >> Tomas Hlavaty <tom@logand.com> writes: >>> >>> I can see these cases where garbage collection might not do its job: >>> >>> 1. imprecise gc >>> >>> 2. program exit or abort >>> >>> 3. a leak to be fixed >> >> 4. the gc is generational and the object is in an old generation >> >> 5. the gc is incremental and didn't get around to the object yet >> >> ... etc >> >> I'm sure you knew this already, but in general, using gc for non-memory >> resource management (e.g., "please close this file when this Lisp object >> is GCed") is not a good idea--depending on the GC behavior, you'll run >> out of file handles or whatnot. The RAII pattern in C++ >> deterministically calls a destructor when a stack-allocated object goes >> out of scope; in Lisp, the various `with-foo' macros serve the same >> purpose. > > But the context here is a "bug report" about a finalizer not being > called and it seemed pretty clear that the call to `garbage-collect` was > just there to try and make the bug more apparent, not because the real > ELisp code relies on the finalizer being called right at the first call > to `garbage-collect` after the object became unreachable. yes, that was the original issue ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 14:23 ` Rudolf Schlatte 2021-12-31 16:21 ` Stefan Monnier @ 2022-01-01 22:36 ` Tomas Hlavaty 2022-01-02 6:54 ` Eli Zaretskii 2022-01-02 18:11 ` Rudolf Schlatte 1 sibling, 2 replies; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-01 22:36 UTC (permalink / raw) To: Rudolf Schlatte, emacs-devel On Fri 31 Dec 2021 at 15:23, Rudolf Schlatte <rudi@constantly.at> wrote: > I'm sure you knew this already, but in general, using gc for non-memory > resource management (e.g., "please close this file when this Lisp object > is GCed") is not a good idea--depending on the GC behavior, you'll run > out of file handles or whatnot. The RAII pattern in C++ > deterministically calls a destructor when a stack-allocated object goes > out of scope; in Lisp, the various `with-foo' macros serve the same > purpose. This is another topic. Somehow it seems very controversial. Some even violently oppose the idea of using gc for non-memory resources but I have not found anybody yet who would help me to understand real deep pros and cons and let me form my own opinion without forcing his own. Following stack discipline is great. Most of the time. But it comes with severe restrictions and consequences. There is a reason why gc was invented. For use-cases, which are not covered by with-foo stuff, one can implement own ad-hoc resource management, or reuse gc. Thus somehow there seems to be a need for finalizers. The negative consequences of following the stack discipline can be seen in many places. Lets have a look at the function directory-files. It is likely the worst possible API for accessing filesystem. (Common Lisp has the same flaw.) It opens a directory, does its work and closes the directory. Nice, it is done "properly": open, work, close. The problem is, that the amount of work done inside open/close is not under control of the programmer and is not bound in space and time. Additionally, the whole work needs to be done completely, or completely aborted. It means that unless the amount of work is trivial, the whole thing and all the things that are built on top of it are useless. That sounds a bit extreme claim. Lets test it with something real: M-x find-lisp-find-dired /nix/store [.]service$. While Emacs blocked, no results seen. C-g after several seconds. That was not very useful. Lets try M-x find-dired /nix/store -name '*.service'. That works nicely. Why? Because the directory traversal runs in a second process (the control loop pushes the results out of the process step by step) and the first emacs process displays the results step by step as they come (with directory entry granularity). How could find-lisp-find-dired be fixed? Could it be fixed while still using directory-files? I do not think so. I guess it can only be fixed by pushing directory entries out from another thread (like find-dired does) or using a "stream" of directory entries pulled on demand. But when should close be called, when such stream is lazy? I am sure a reasonable ad-hoc solution could be implemented. But what if the directory is traversed recursively? When should each close be called? A case for another ad-hoc solution? Looks like finalizers would be great here. Yes there would be issues, like hitting the open file limit. But are those issues show stoppers? Could there be a useful solution? Maybe run gc and try to open the file again? Or something more sophisticated? Have somebody explored this area? ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 22:36 ` Tomas Hlavaty @ 2022-01-02 6:54 ` Eli Zaretskii 2022-01-02 7:53 ` Tomas Hlavaty 2022-01-02 18:11 ` Rudolf Schlatte 1 sibling, 1 reply; 42+ messages in thread From: Eli Zaretskii @ 2022-01-02 6:54 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: rudi, emacs-devel > From: Tomas Hlavaty <tom@logand.com> > Date: Sat, 01 Jan 2022 23:36:10 +0100 > > Lets have a look at the function directory-files. It is likely the > worst possible API for accessing filesystem. (Common Lisp has the same > flaw.) It opens a directory, does its work and closes the directory. > Nice, it is done "properly": open, work, close. The problem is, that > the amount of work done inside open/close is not under control of the > programmer and is not bound in space and time. Additionally, the whole > work needs to be done completely, or completely aborted. It means that > unless the amount of work is trivial, the whole thing and all the things > that are built on top of it are useless. That sounds a bit extreme > claim. Lets test it with something real: M-x find-lisp-find-dired > /nix/store [.]service$. While Emacs blocked, no results seen. C-g > after several seconds. That was not very useful. Lets try M-x > find-dired /nix/store -name '*.service'. That works nicely. Why? > Because the directory traversal runs in a second process (the control > loop pushes the results out of the process step by step) and the first > emacs process displays the results step by step as they come (with > directory entry granularity). How could find-lisp-find-dired be fixed? > Could it be fixed while still using directory-files? I do not think so. > I guess it can only be fixed by pushing directory entries out from > another thread (like find-dired does) or using a "stream" of directory > entries pulled on demand. One common paradigm for such use cases is to use a callback: the function that traverses some collection of objects calls that callback for every object it finds. There are usually protocols for the callback to stop the process and prevent the rest of objects from being examined/reported. > But when should close be called, when such stream is lazy? I am > sure a reasonable ad-hoc solution could be implemented. But what if > the directory is traversed recursively? When should each close be > called? A case for another ad-hoc solution? Looks like finalizers > would be great here. We have the unwind-protect machinery in place for that. In fact, if you look at the implementation of directory-files, you will see that it already uses that machinery, and it most probably actually kicked in when you typed C-g above. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-02 6:54 ` Eli Zaretskii @ 2022-01-02 7:53 ` Tomas Hlavaty 2022-01-02 8:33 ` Eli Zaretskii 0 siblings, 1 reply; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-02 7:53 UTC (permalink / raw) To: Eli Zaretskii; +Cc: rudi, emacs-devel On Sun 02 Jan 2022 at 08:54, Eli Zaretskii <eliz@gnu.org> wrote: > One common paradigm for such use cases is to use a callback: the > function that traverses some collection of objects calls that callback > for every object it finds. There is no such thing in directory-files. Also using callbacks in directory-files alone would not fix find-lisp-find-dired. I think an extra execution thread would be needed. Maybe using an extra execution thread could be seen as a work around for the negative consequences of stack discipline. If there was a callback in directory-files, it would allow for pushing directory items out of the execution thread when encountered, instead of waiting until the contents of the whole directory has been collected. > There are usually protocols for the callback to stop the process and > prevent the rest of objects from being examined/reported. There is no such thing in directory-files. I can't see such thing in any other mapping functions in Emacs Lisp. Could you point me to an example? Also, such protocols are non-trivial and usually complicate the code significantly. See for example how people deal with it JavaScript, where they have no other choice. >> But when should close be called, when such stream is lazy? I am >> sure a reasonable ad-hoc solution could be implemented. But what if >> the directory is traversed recursively? When should each close be >> called? A case for another ad-hoc solution? Looks like finalizers >> would be great here. > > We have the unwind-protect machinery in place for that. In fact, if > you look at the implementation of directory-files, you will see that > it already uses that machinery, and it most probably actually kicked > in when you typed C-g above. I see I did not manage to get the point across. directory-files is "eager" and follows stack discipline so unwind-protect there makes sense. But the idea of directory-files itself is broken as I demonstrated previously. Operating systems get it right and provide streaming API with open/read/close directory functions. Also the brokenness of directory-files propagates to many other points in Emacs that need to list directories, like find-lisp-find-dired and completion. But the point here was that in case of lazy sequences, the point in time where close should be called is not known upfront. One would have to split the code into two parts: eager one which would force stack discipline and lazy one which would do the lazy part. Maybe doable for trivial thing like lazy listing of a single directory, but it gets harder when doing something non-trivial like lazy listing of a directory recursively. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-02 7:53 ` Tomas Hlavaty @ 2022-01-02 8:33 ` Eli Zaretskii 2022-01-02 13:10 ` Tomas Hlavaty 0 siblings, 1 reply; 42+ messages in thread From: Eli Zaretskii @ 2022-01-02 8:33 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: rudi, emacs-devel > From: Tomas Hlavaty <tom@logand.com> > Cc: rudi@constantly.at, emacs-devel@gnu.org > Date: Sun, 02 Jan 2022 08:53:31 +0100 > > On Sun 02 Jan 2022 at 08:54, Eli Zaretskii <eliz@gnu.org> wrote: > > One common paradigm for such use cases is to use a callback: the > > function that traverses some collection of objects calls that callback > > for every object it finds. > > There is no such thing in directory-files. It should be easy to add, if we wanted to. Or we could implement a separate set of primitives using that, if it were deemed useful. I fail to see how what's in Emacs now is of any essence for the purposes of this discussion. What exactly are you getting at, and how is what is in Emacs today relevant? Are you saying that Emacs couldn't possibly provide such primitives due to some inherent limitation? If so, please explain why you think so, because I don't think I agree. > Also using callbacks in directory-files alone would not fix > find-lisp-find-dired. You'd need a different implementation of that, of course. Other than that, what is your point? > I think an extra execution thread would be needed. I don't see why. A callback can easily insert files into the buffer, and trigger redisplay either directly or via timers and such. Keep in mind that when Emacs invokes an async subprocess, the display of what that subprocess produces is still done in the same single thread where the Lisp machine runs. So we already have the capabilities of "pretending" we have multithreading, when these aspects are involved. > > There are usually protocols for the callback to stop the process and > > prevent the rest of objects from being examined/reported. > > There is no such thing in directory-files. I think we have something like that in lisp/url/. > Also, such protocols are non-trivial and usually complicate the code > significantly. I don't see why this would be non-trivial. In the simplest case, the callback returns a special value which tells to stop the iteration. > > We have the unwind-protect machinery in place for that. In fact, if > > you look at the implementation of directory-files, you will see that > > it already uses that machinery, and it most probably actually kicked > > in when you typed C-g above. > > I see I did not manage to get the point across. directory-files is > "eager" and follows stack discipline so unwind-protect there makes > sense. But the idea of directory-files itself is broken as I > demonstrated previously. Operating systems get it right and provide > streaming API with open/read/close directory functions. Also the > brokenness of directory-files propagates to many other points in Emacs > that need to list directories, like find-lisp-find-dired and completion. Emacs is not an OS (the popular myth about that notwithstanding). It provides primitives that are useful for writing editing and text-processing applications. What you don't find in Emacs is probably outside of its scope, or at least is rarely if ever needed. > But the point here was that in case of lazy sequences, the point in time > where close should be called is not known upfront. One would have to > split the code into two parts: eager one which would force stack > discipline and lazy one which would do the lazy part. Maybe doable for > trivial thing like lazy listing of a single directory, but it gets > harder when doing something non-trivial like lazy listing of a directory > recursively. Where you see something non-trivial, I see just simple use of our unwinding infrastructure. There's no problem I see with using that infrastructure in recursive processing of directories. If you see such problems, please tell what they are, specifically, given the unwind_protect functions we have and the mechanism that actually implements the unwinding. In Emacs, many operations could signal an error or throw, and the user could type C-g at any moment, so this mechanism is in use all the time, and must handle correctly nested unwinders. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-02 8:33 ` Eli Zaretskii @ 2022-01-02 13:10 ` Tomas Hlavaty 2022-01-02 14:42 ` Eli Zaretskii 0 siblings, 1 reply; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-02 13:10 UTC (permalink / raw) To: Eli Zaretskii; +Cc: rudi, emacs-devel On Sun 02 Jan 2022 at 10:33, Eli Zaretskii <eliz@gnu.org> wrote: >> From: Tomas Hlavaty <tom@logand.com> >> Cc: rudi@constantly.at, emacs-devel@gnu.org >> Date: Sun, 02 Jan 2022 08:53:31 +0100 >> >> On Sun 02 Jan 2022 at 08:54, Eli Zaretskii <eliz@gnu.org> wrote: >> > One common paradigm for such use cases is to use a callback: the >> > function that traverses some collection of objects calls that callback >> > for every object it finds. >> >> There is no such thing in directory-files. > > It should be easy to add, if we wanted to. Or we could implement a > separate set of primitives using that, if it were deemed useful. > > I fail to see how what's in Emacs now is of any essence for the > purposes of this discussion. What exactly are you getting at, and how > is what is in Emacs today relevant? Are you saying that Emacs > couldn't possibly provide such primitives due to some inherent > limitation? If so, please explain why you think so, because I don't > think I agree. I was replying to Rudolf about the use-cases where stack discipline can have visible negative consequences and demonstrating it with a few examples in Emacs. It is also related to the use of finalizers as an alternative mechanism to stack discipline. I am saying that there are things that could be improved: - Emacs Lisp should provide open-directory, read-directory and close-directory functions. I think that is the most important point. There are other related observations, like: - find-lisp-find-dired is fundamentally broken. - the brokenness is due to directory-files being the wrong API. It prevents people from writing better code. - the brokenness propagates to everything which uses directory-files, like find-lisp-find-dired, completion (e.g. completion on /nix/store blocks emacs completely for significant amount of time). Now I see that even lisp/url uses directory-files thus there are cases where it will be unuseable. - directory-files implies consing all the directory entries except a few pre-hardcoded filter use-cases. It does not allow anything else to be more efficient, where one would want to filter out unneeded directory entries at the moment they are first encountered. - return_count argument to directory_files_internal shows a desperate attempt to fix patological deficiency:-) return_count is missing from directory-files-recursively. return_count still does not provide sufficient control of previously uncontrollable behaviour. - directory-files-recursively brings yet another different interface for traversing file system - directory-files-recursively seems to be built on different primitive file-name-all-completions I do not understand the implementation of file-name-all-completions yet but from the observed behaviour, it seems to have the same flaws like directory-files. > I don't see why. A callback can easily insert files into the buffer, > and trigger redisplay either directly or via timers and such. But the control is in the hand of the mapping function. Triggering redisplay is not sufficient for it to behave like in find-dired, I think. It needs to handle input and everything as if it was running in a different execution thread, like find-dired does with the "mapping function" running in an external process (find program). > Keep in mind that when Emacs invokes an async subprocess, the display > of what that subprocess produces is still done in the same single > thread where the Lisp machine runs. So we already have the > capabilities of "pretending" we have multithreading, when these > aspects are involved. Yes, it works well when the mapping function (in the case of find-dired, the find process) runs in a different execution thread/process. It does not work well, when it runs in the same single execution thread. See find-lisp-find-dired. >> > There are usually protocols for the callback to stop the process and >> > prevent the rest of objects from being examined/reported. >> >> There is no such thing in directory-files. > > I think we have something like that in lisp/url/. Thanks for the hint. Unfortunatelly I can't find anything from after a quick search. I can see that it uses directory-files which means it also inherits its brokenness in those cases. >> Also, such protocols are non-trivial and usually complicate the code >> significantly. > > I don't see why this would be non-trivial. In the simplest case, the > callback returns a special value which tells to stop the iteration. The simplest case is also the least interesting. > Emacs is not an OS (the popular myth about that notwithstanding). It > provides primitives that are useful for writing editing and > text-processing applications. What you don't find in Emacs is > probably outside of its scope, or at least is rarely if ever needed. Directory traversal seems to be essential part of Emacs. >> But the point here was that in case of lazy sequences, the point in time >> where close should be called is not known upfront. One would have to >> split the code into two parts: eager one which would force stack >> discipline and lazy one which would do the lazy part. Maybe doable for >> trivial thing like lazy listing of a single directory, but it gets >> harder when doing something non-trivial like lazy listing of a directory >> recursively. > > Where you see something non-trivial, I see just simple use of our > unwinding infrastructure. There's no problem I see with using that > infrastructure in recursive processing of directories. If you see > such problems, please tell what they are, specifically, given the > unwind_protect functions we have and the mechanism that actually > implements the unwinding. In Emacs, many operations could signal an > error or throw, and the user could type C-g at any moment, so this > mechanism is in use all the time, and must handle correctly nested > unwinders. Problem is not with unwind-protect. Problem is with directory-files and all functions that use it. I think I pointed out the problems sufficiently. One fix could be implementing an alternative to directory-files, e.g. directory mapping function, plus running it in a extra thread. Another possibility would be to traverse the directory lazily, but that brings an issue of eagerly closing directories. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-02 13:10 ` Tomas Hlavaty @ 2022-01-02 14:42 ` Eli Zaretskii 2022-01-02 15:16 ` Eli Zaretskii 0 siblings, 1 reply; 42+ messages in thread From: Eli Zaretskii @ 2022-01-02 14:42 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: rudi, emacs-devel > From: Tomas Hlavaty <tom@logand.com> > Cc: rudi@constantly.at, emacs-devel@gnu.org > Date: Sun, 02 Jan 2022 14:10:02 +0100 > > - Emacs Lisp should provide open-directory, read-directory and > close-directory functions. If real-life use cases are brought up that cannot be adequately handled by the existing APIs, we will consider extending them or adding new APIs. But the use cases need to come first. > > I don't see why. A callback can easily insert files into the buffer, > > and trigger redisplay either directly or via timers and such. > > But the control is in the hand of the mapping function. No, the callback can do that under its own control. > Triggering redisplay is not sufficient for it to behave like in > find-dired, I think. It needs to handle input and everything as if it > was running in a different execution thread, like find-dired does with > the "mapping function" running in an external process (find program). > > > Keep in mind that when Emacs invokes an async subprocess, the display > > of what that subprocess produces is still done in the same single > > thread where the Lisp machine runs. So we already have the > > capabilities of "pretending" we have multithreading, when these > > aspects are involved. > > Yes, it works well when the mapping function (in the case of find-dired, > the find process) runs in a different execution thread/process. > > It does not work well, when it runs in the same single execution thread. Like I said: output from the sub-process is processed and displayed by the same single-thread, without any help from additional threads. the subprocess produces output, but it is only displayed by Emacs when we read that output and process it, which is done by the same single thread which you say cannot do the job. So the same can be done without sub-processes. If needed, of course. > See find-lisp-find-dired. Not relevant: that function was not designed provide output during the search, so it doesn't. > >> > There are usually protocols for the callback to stop the process and > >> > prevent the rest of objects from being examined/reported. > >> > >> There is no such thing in directory-files. > > > > I think we have something like that in lisp/url/. > > Thanks for the hint. Unfortunatelly I can't find anything from after a > quick search. Grep for "callback". > > I don't see why this would be non-trivial. In the simplest case, the > > callback returns a special value which tells to stop the iteration. > > The simplest case is also the least interesting. If it does the job, why isn't it interesting? > > Emacs is not an OS (the popular myth about that notwithstanding). It > > provides primitives that are useful for writing editing and > > text-processing applications. What you don't find in Emacs is > > probably outside of its scope, or at least is rarely if ever needed. > > Directory traversal seems to be essential part of Emacs. And it does work. > > Where you see something non-trivial, I see just simple use of our > > unwinding infrastructure. There's no problem I see with using that > > infrastructure in recursive processing of directories. If you see > > such problems, please tell what they are, specifically, given the > > unwind_protect functions we have and the mechanism that actually > > implements the unwinding. In Emacs, many operations could signal an > > error or throw, and the user could type C-g at any moment, so this > > mechanism is in use all the time, and must handle correctly nested > > unwinders. > > Problem is not with unwind-protect. I didn't mean unwind-protect (in Lisp), I meant unwind_protect (in C). We are talking about primitives that should be implemented in C, yes? > Problem is with directory-files and all functions that use it. I think > I pointed out the problems sufficiently. And I think I explained why I think your assessment is mistaken. > One fix could be implementing an alternative to directory-files, > e.g. directory mapping function, plus running it in a extra thread. You are welcome to try that, but I don't think it will work in Emacs, not with the Lisp threads we have. > Another possibility would be to traverse the directory lazily, but that > brings an issue of eagerly closing directories. See above: I see no issue. They could/should be closed in unwind_protect functions. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-02 14:42 ` Eli Zaretskii @ 2022-01-02 15:16 ` Eli Zaretskii 2022-01-02 17:18 ` Tomas Hlavaty 0 siblings, 1 reply; 42+ messages in thread From: Eli Zaretskii @ 2022-01-02 15:16 UTC (permalink / raw) To: tom; +Cc: rudi, emacs-devel > Date: Sun, 02 Jan 2022 16:42:45 +0200 > From: Eli Zaretskii <eliz@gnu.org> > Cc: rudi@constantly.at, emacs-devel@gnu.org > > > >> > There are usually protocols for the callback to stop the process and > > >> > prevent the rest of objects from being examined/reported. > > >> > > >> There is no such thing in directory-files. > > > > > > I think we have something like that in lisp/url/. > > > > Thanks for the hint. Unfortunatelly I can't find anything from after a > > quick search. > > Grep for "callback". Come to think of it, calling hooks via run-hook-with-args-until-failure or run-hook-with-args-until-success implements such a protocol. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-02 15:16 ` Eli Zaretskii @ 2022-01-02 17:18 ` Tomas Hlavaty 0 siblings, 0 replies; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-02 17:18 UTC (permalink / raw) To: Eli Zaretskii; +Cc: rudi, emacs-devel On Sun 02 Jan 2022 at 17:16, Eli Zaretskii <eliz@gnu.org> wrote: >> Grep for "callback". > > Come to think of it, calling hooks via > run-hook-with-args-until-failure or run-hook-with-args-until-success > implements such a protocol. thank you, I'll have a look ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-01 22:36 ` Tomas Hlavaty 2022-01-02 6:54 ` Eli Zaretskii @ 2022-01-02 18:11 ` Rudolf Schlatte 2022-01-03 0:37 ` Tomas Hlavaty 1 sibling, 1 reply; 42+ messages in thread From: Rudolf Schlatte @ 2022-01-02 18:11 UTC (permalink / raw) To: emacs-devel Tomas Hlavaty <tom@logand.com> writes: > On Fri 31 Dec 2021 at 15:23, Rudolf Schlatte <rudi@constantly.at> wrote: >> I'm sure you knew this already, but in general, using gc for non-memory >> resource management (e.g., "please close this file when this Lisp object >> is GCed") is not a good idea--depending on the GC behavior, you'll run >> out of file handles or whatnot. The RAII pattern in C++ >> deterministically calls a destructor when a stack-allocated object goes >> out of scope; in Lisp, the various `with-foo' macros serve the same >> purpose. > > This is another topic. > > Somehow it seems very controversial. Some even violently oppose the > idea of using gc for non-memory resources but I have not found anybody > yet who would help me to understand real deep pros and cons and let me > form my own opinion without forcing his own. I apologize for derailing the thread a bit, and acknowledge that I did not really engage with your concrete problem (this was also pointed out by Stefan M, thanks for this). That being said, I'll try to express, in one exaggerated sentence, the deep cons of using gc for non-memory resources in the hope that it helps a bit: ”If you rely on the garbage collector to release non-memory resources as a side effect of reclaiming memory, you will run out of resources if you increase the main memory of your system.” ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-02 18:11 ` Rudolf Schlatte @ 2022-01-03 0:37 ` Tomas Hlavaty 2022-01-04 3:08 ` Richard Stallman 0 siblings, 1 reply; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-03 0:37 UTC (permalink / raw) To: Rudolf Schlatte, emacs-devel On Sun 02 Jan 2022 at 19:11, Rudolf Schlatte <rudi@constantly.at> wrote: > That being said, I'll try to express, in one exaggerated sentence, the > deep cons of using gc for non-memory resources in the hope that it > helps a bit: > > ”If you rely on the garbage collector to release non-memory resources as > a side effect of reclaiming memory, you will run out of resources if you > increase the main memory of your system.” Is that your sentence? Or could you point me to the source for further reading? I agree that the huge inbalance between the amount of available memory and the amount of available file descriptors is a problem. But the sentence does not explain the details. Is it the consequence of Lisp running on a hosted operating system with those constraints? Did for example lisp machines have the same constraints? Why? Could EMFILE and ENFILE trigger gc and then retry open? sbcl does finalize file descriptors and iirc some schemes close them automatically too. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2022-01-03 0:37 ` Tomas Hlavaty @ 2022-01-04 3:08 ` Richard Stallman 0 siblings, 0 replies; 42+ messages in thread From: Richard Stallman @ 2022-01-04 3:08 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: rudi, emacs-devel [[[ To any NSA and FBI agents reading my email: please consider ]]] [[[ whether defending the US Constitution against all enemies, ]]] [[[ foreign or domestic, requires you to follow Snowden's example. ]]] > > ”If you rely on the garbage collector to release non-memory resources as > > a side effect of reclaiming memory, you will run out of resources if you > > increase the main memory of your system.” That would be true if the criterion for when to GC is that "memory is running low". Increase the total memory size, and it won't run low very often, so GC will happen less often. But in Emacs GC is triggered by allocating a certain amount of memory. Thus, the thing that would hold the non-memory resources too long is increasing gc-cons-threshold. -- Dr Richard Stallman (https://stallman.org) Chief GNUisance of the GNU Project (https://gnu.org) Founder, Free Software Foundation (https://fsf.org) Internet Hall-of-Famer (https://internethalloffame.org) ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-30 22:43 using finalizers Tomas Hlavaty 2021-12-31 2:46 ` LdBeth @ 2021-12-31 14:39 ` LdBeth 2022-01-01 17:59 ` Tomas Hlavaty 1 sibling, 1 reply; 42+ messages in thread From: LdBeth @ 2021-12-31 14:39 UTC (permalink / raw) To: Tomas Hlavaty; +Cc: emacs-devel >>>>> In <878rw1pvcw.fsf@logand.com> >>>>> Tomas Hlavaty <tom@logand.com> wrote: Tomas> The interface is different from Common Lisp, where make-finalizer Tomas> usually expects object and thunk, not thunk only but I guess the problem Tomas> lies somewhere else. If you mean you are looking for a finalizer interface similar to what Common Lisp's trivial-garbarge library provides, there was one in Emacs Lisp before `make-finalizer' was provided. https://nullprogram.com/blog/2014/01/27/ Both that one and trivial-garbarge are based on weak references. However that is actually not very good because that impose a lot of jobs on GC, at least for Emacs the garbage collector is not as good as many Common Lisp's ones. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: using finalizers 2021-12-31 14:39 ` LdBeth @ 2022-01-01 17:59 ` Tomas Hlavaty 0 siblings, 0 replies; 42+ messages in thread From: Tomas Hlavaty @ 2022-01-01 17:59 UTC (permalink / raw) To: LdBeth; +Cc: emacs-devel On Fri 31 Dec 2021 at 22:39, LdBeth <andpuke@foxmail.com> wrote: > If you mean you are looking for a finalizer interface similar to what > Common Lisp's trivial-garbarge library provides, there was one in > Emacs Lisp before `make-finalizer' was provided. > > https://nullprogram.com/blog/2014/01/27/ > > Both that one and trivial-garbarge are based on weak references. > However that is actually not very good because that impose a lot of > jobs on GC, at least for Emacs the garbage collector is not as good as > many Common Lisp's ones. yes, that seems like the thing I was after thanks for the material for further study ^ permalink raw reply [flat|nested] 42+ messages in thread
end of thread, other threads:[~2022-01-04 3:08 UTC | newest] Thread overview: 42+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2021-12-30 22:43 using finalizers Tomas Hlavaty 2021-12-31 2:46 ` LdBeth 2021-12-31 3:29 ` Stefan Monnier 2021-12-31 3:36 ` Stefan Monnier 2021-12-31 4:30 ` LdBeth 2021-12-31 4:43 ` LdBeth 2021-12-31 6:28 ` Tomas Hlavaty 2021-12-31 10:59 ` LdBeth 2021-12-31 11:18 ` Tomas Hlavaty 2021-12-31 11:41 ` LdBeth 2021-12-31 12:01 ` Tomas Hlavaty 2022-01-01 17:00 ` Stefan Monnier 2022-01-01 20:25 ` Tomas Hlavaty 2022-01-01 20:47 ` Stefan Monnier 2022-01-01 22:55 ` Tomas Hlavaty 2022-01-01 23:18 ` Stefan Monnier 2022-01-01 23:47 ` Tomas Hlavaty 2022-01-01 23:26 ` Tomas Hlavaty 2021-12-31 7:50 ` Eli Zaretskii 2021-12-31 9:31 ` Tomas Hlavaty 2021-12-31 12:27 ` Eli Zaretskii 2022-01-01 17:58 ` Tomas Hlavaty 2022-01-01 18:20 ` Eli Zaretskii 2022-01-01 20:55 ` Stefan Monnier 2022-01-01 23:05 ` Tomas Hlavaty 2022-01-01 23:21 ` Stefan Monnier 2021-12-31 14:23 ` Rudolf Schlatte 2021-12-31 16:21 ` Stefan Monnier 2022-01-01 17:37 ` Tomas Hlavaty 2022-01-01 22:36 ` Tomas Hlavaty 2022-01-02 6:54 ` Eli Zaretskii 2022-01-02 7:53 ` Tomas Hlavaty 2022-01-02 8:33 ` Eli Zaretskii 2022-01-02 13:10 ` Tomas Hlavaty 2022-01-02 14:42 ` Eli Zaretskii 2022-01-02 15:16 ` Eli Zaretskii 2022-01-02 17:18 ` Tomas Hlavaty 2022-01-02 18:11 ` Rudolf Schlatte 2022-01-03 0:37 ` Tomas Hlavaty 2022-01-04 3:08 ` Richard Stallman 2021-12-31 14:39 ` LdBeth 2022-01-01 17:59 ` Tomas Hlavaty
Code repositories for project(s) associated with this external index https://git.savannah.gnu.org/cgit/emacs.git https://git.savannah.gnu.org/cgit/emacs/org-mode.git This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.