Re: How should we deal with weak refs to finalizable objects? (was: Re: [PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events))

2023-07-27 Thread Peter Bex
On Thu, Jul 27, 2023 at 12:30:46PM +0200, Peter Bex wrote:
> 1) There's no (efficient) way to know if an object is a finalizable one.
>We need this because we can't simply clear *all* the objects inside
>a finalizable object that aren't referenced elsewhere, because we do
>want to keep foreign pointers etc which are only referenced by the
>finalized object itself.  So we'd need an efficient way to know if
>an object pointed to by a finalizable object is itself finalizable.

Strike that - I think it can be done at the cost of an additional
pointer per finalizer to encode the boundaries of objects that belong to
that finalizer's "reachable set".  But still, that leaves us with #2.

> 2) We have no (canonical) way of breaking strong references.  For weak
>references, it is clear that we have some special indicator, the
>"broken weak pointer" placeholder that gets put there when an object
>reference is cleared.

Cheers,
Peter


signature.asc
Description: PGP signature


Re: How should we deal with weak refs to finalizable objects? (was: Re: [PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events))

2023-07-27 Thread Peter Bex
On Wed, Jul 26, 2023 at 04:45:53PM +0100, Andy Bennett wrote:
> So how can we finalise a circular list of objects all of which have
> finalisers and still maintain atomicity?
> 
> The docs say the order is "undefined".
> It seems that the best way to finalise this structure is to explicitly break
> all the strong references between components of the list (as we do for
> external weak references) before any of the finalisers are called.

I thought about this one too - it would be nice if all the finalized objects
that refer to other finalized objects would have these links cleared.
However, there are two main obstacles to that:

1) There's no (efficient) way to know if an object is a finalizable one.
   We need this because we can't simply clear *all* the objects inside
   a finalizable object that aren't referenced elsewhere, because we do
   want to keep foreign pointers etc which are only referenced by the
   finalized object itself.  So we'd need an efficient way to know if
   an object pointed to by a finalizable object is itself finalizable.

2) We have no (canonical) way of breaking strong references.  For weak
   references, it is clear that we have some special indicator, the
   "broken weak pointer" placeholder that gets put there when an object
   reference is cleared.

> It may also improve the memory model if we define the object that the
> finaliser receives as a "copy" of the object that has ("already") been
> garbage collected.

I don't really think this will make much of a difference either way - as
it is currently, you couldn't distinguish the "original" object from a
copy.

Possibly a better way is what MIT Scheme (and, gasp, JavaScript) does:
register a finalizer on object with an extraction procedure that returns
the value to finalize.  That way, the object getting deleted is not the
object that is getting finalized.  For instance, when a port would be
finalized, the finalization procedure receives the underlying file
descriptor and not the entire port object.

Although after giving that some more thought, I'm not 100% sure this
would really solve the issue - you can still extract complex objects
from the to-be-finalized object (or have the "identity" procedure as
the "extractor", so it returns the object itself) and therefore have
an object with other things pointing into it, and that could still
be resurrected...

Cheers,
Peter


signature.asc
Description: PGP signature


Re: How should we deal with weak refs to finalizable objects? (was: Re: [PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events))

2023-07-26 Thread Andy Bennett

Hi,


I think this clears things sufficiently up.


Please excuse me if I am missing some details...



Strong references are already ensured to be gone
and weak references are cleared using the incantations that sjamaan
(in his wisdom) proposed


What is the definition of a weak reference in this case?


One definition is that weak references only refer to the object if there 
are one or more strong references to the object.


In this case, all the strong references are gone before the finaliser runs 
and therefore all the weak references are semantically invalid already.


Whilst the finaliser is running there are no strong references to the 
object but it's possible (if the finaliser decides to resurrect the object) 
that there will once again be a strong reference to the object (e.g. if the 
finaliser set!s it).


But then, as you say, it's reincarnated: a new object that will never be 
more than equal? (and certainly not eqv? or eq?) to the old one.


Everything's consistent in this model because the references that the GC 
has to objects are neither "weak" nor "strong" but merely an implementation 
detail hidden from the outside.


This seems to be the definition that sjamaan is using.



Another definition is that weak references can refer to an object that is 
still "in memory".


This way lies disaster because then the semantics of weak references are 
dependent on specific implementation details.





I think here we're struggling with the atomicity of the garbage collector 
because the finaliser is special user code that executes inside the garbage 
collector's "transaction" and that code has all the power and capabilities 
of any other scheme code.




What happens if there are a circular list of objects all of which have 
finalisers?


The CHICKEN GC usually handles circular lists well, but when this one is 
collected (i.e. there are no external strong references to the list 
anymore) all its members come up for finalisation at the same time.


Which order are the finalisers called in?

...and which of the other objects can be seen from the object that is 
finalised first?



This is similar to the argument above about the correct definition of weak 
pairs.


To preserve atomicity there needs to be no "happens before" or "happens 
after" relationships between things that happen in a single GC cycle 
(transaction).


If weak pairs are not *already* invalid by the time the finaliser runs then 
it's possible to see into the GC transaction because the weak pair 
invalidation can be observed to "happen after" the GC cycle.


When the managed memory for the circular list is freed it does not matter 
precisely which order it is freed in because there are no side effects of 
freeing it and it all happens in a single GC cycle.


But when the finalisers are run the user's code will see these objects in 
various different states depending on the order.



So how can we finalise a circular list of objects all of which have 
finalisers and still maintain atomicity?


The docs say the order is "undefined".
It seems that the best way to finalise this structure is to explicitly 
break all the strong references between components of the list (as we do 
for external weak references) before any of the finalisers are called.




It may also improve the memory model if we define the object that the 
finaliser receives as a "copy" of the object that has ("already") been 
garbage collected.






Best wishes,
@ndy

--
andy...@ashurst.eu.org
http://www.ashurst.eu.org/
0x7EBA75FF



[PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events)

2023-07-11 Thread felix . winkelmann
Pushed. I pushed another commit to handle bwp immediates
introduced during compilation, without this compiling the weak-pointer-test
failed for me.


felix




Re: How should we deal with weak refs to finalizable objects? (was: Re: [PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events))

2023-07-11 Thread Peter Bex
On Mon, Jul 10, 2023 at 09:28:19PM +0200, felix.winkelm...@bevuta.com wrote:
> After thinking some more about this, I realize that your approach
> (clearing weak ref's to finalized data) is the right thing, since
> any other behaviour in the presence of multithreading leads to
> disaster.

Thank you for seeing the light.

Cheers,
Peter


signature.asc
Description: PGP signature


How should we deal with weak refs to finalizable objects? (was: Re: [PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events))

2023-07-10 Thread felix . winkelmann
After thinking some more about this, I realize that your approach
(clearing weak ref's to finalized data) is the right thing, since
any other behaviour in the presence of multithreading leads to
disaster.

Let me elaborate.

Finalization is a time of reckoning, a purgatory where an object
undergoes a final cleansing of possibly sinful state, of references
to things foreign and alien, the dirty underbelly of an objects
existence that must be brought to order in ways only the user can
truly know about. Since the object is in this state, all its wordly
connections have already ceased to exist, its identity forgotten
(or it wouldn't be ready for reclamation).

But what if other threads access a weakly remembered object
while it is in purgatory? They would deal with an empty husk, a ghost,
likely to be devoid of the things (external pointers and other resources)
that define its true self, a mere shadow, with consequences that do not
have to be explicitly mentioned here and are better left unsaid.

Should the user (for reasons we can and must never know) decide that
the object is not ready yet to go to the other world and should stay
for another cycle of suffering in this earthly existence and store the
value in some external location, then the object will internally be
the same and have the same true identity, but external pointers will
have ceased to exist. Strong references are already ensured to be gone
and weak references are cleared using the incantations that sjamaan
(in his wisdom) proposed (certain enlightened objects that have a
sufficiently advanced self conciousness may know their true identity,
i.e. keep circular references to itself, but these must necessarily be
internal and are irrelevant when seem from outside).

So reincarnation means the object _is_ identical, but the nature
of its identity is invisible to the outside world. Enlightened objects
may know about their true identity but trying to communicate that
beyond its inner self is meaningless when seen from the outside.

I think this clears things sufficiently up.


felix




How should we deal with weak refs to finalizable objects? (was: Re: [PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events))

2023-07-10 Thread felix . winkelmann
> However, there's one more concern:
>
> > The potential use-after-free scenario can still happen if the object is
> > kept alive, regardless of how we handle weak refs, this is unavoidable
> > if we allow finalizers and keep the possibility of resurrection.
>
> I have thought about this a bit more but I came to the conclusion that
> from an abstraction point of view it's better to clear weak refs to
> finalized data.  The reason is that when a module exposes an object, the
> *user* should not need to know or care exactly how that object is
> implemented and that it happens to use a finalizer.
>

I concur - it indeed breaks the abstraction. IHMO both behaviours (clearing
weak refs or not) have potential to confuse the user, but merely adding a
finalizer should not semantically change the behaviour of weak refs.
Let me think a bit more about this, please.


felix




How should we deal with weak refs to finalizable objects? (was: Re: [PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events))

2023-07-10 Thread Peter Bex
On Fri, Jul 07, 2023 at 11:23:17PM +0200, felix.winkelm...@bevuta.com wrote:
> I'm not very comfortable with this change. This feels like trading in
> one inconsistency (weak refs being cleared for a potentially non-dead
> object) for another (potentially inconsistent ties of GC-controlled
> memory to non-GC'd resources).

It depends on how you view finalizers.  Personally, I would think a
finalizer should get run on what's *essentially* "already GCed" data.
Therefore, it makes no sense to pass a finalizer data that still holds
onto other cleared data.

But like I said, I can get behind your POV - you could also argue that
if an object holds onto other things and it's "already GCed", all the
things it (and nobody else) holds onto (even strongly) should be
cleared, and that absolutely makes no sense whatsoever.

(come to think of it, the ideal solution would probably be to clear
"outside" weak references to finalizable data but keep the object
itself internally intact.  But that's extremely hard to track in the GC)

However, there's one more concern:

> The potential use-after-free scenario can still happen if the object is
> kept alive, regardless of how we handle weak refs, this is unavoidable
> if we allow finalizers and keep the possibility of resurrection.

I have thought about this a bit more but I came to the conclusion that
from an abstraction point of view it's better to clear weak refs to
finalized data.  The reason is that when a module exposes an object, the
*user* should not need to know or care exactly how that object is
implemented and that it happens to use a finalizer.

So let's say an egg exposes an object, and I'm using it, but I want to
reference it weakly.  Then, all things will work fine most of the time.
However, there's a nasty race condition lurking: if the finalizer
happens to run before my code extracts the object from the weak ref,
but I extract it before the next GC, my code may crash.  Or it may not,
use-after-free is tricky like that.

This should not be the user's concern, and it's a breach of abstraction.
It's also a global issue, as it can affect *any* code that holds onto
an object from a "3rd party" (other module).

Note that this would be problematic *even* if the 3rd party wrote the
code so carefully that it clears the pointer from the object such that
there's no use-after-free bug.  Because the code will still raise an
exception when passed this invalidated object.  And again, that will
be a race condition for the person who uses this module.  This means
that the problem is spread to every single user who decides to use a
weak reference to a 3rd party object that involves resources that must
be freed.

On the other hand, if I'm writing an egg that exposes a foreign object
that needs to be collected, it *is* of my concern (and not a breach of
abstraction) that the data gets freed properly.  Let's say I decide to
store weak refs inside the object (which is not even *that* likely),
and those weak refs point to other things inside the same object, which
I know to be GCed at the time the finalizer is called.  Then that's
something I know and must take care of when writing the finalizer.
Finalizers of objects not seeing weak refs into that same object is
very much a localized concernn.

Also, perhaps more importantly, this will break *immediately* and
*consistently*.  The finalizer will simply see broken weak pointers, all
the time, every time it runs on the "collected" object.  Since this is
also documented in my patch, I think this is the least impactful way of
doing things.

I'm not even all that concerned about finalizers "reviving" dead
objects, but in that case it's *definitely* the responsibility of the
finalizer's author to make sure it doesn't cause any trouble.

So overall, IMHO the problem is a lot more self-contained/localized,
and more deterministic if we clear the references before running the
finalizer.  That has to count for something, I think!

Cheers,
Peter


signature.asc
Description: PGP signature


[PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events)

2023-07-07 Thread felix . winkelmann
> On Thu, Jul 06, 2023 at 09:05:03PM +0200, felix.winkelm...@bevuta.com wrote:
> > > This would be problematic if the finalizer has run and deleted the
> > > foreign object, while there are still weak references that hold onto
> > > the object.  This has then become invalid/inconsistent.
> >
> > I don't understand this, I'm afraid. Finalizers can always "revive"
> > objects, this can't be avoided and may sometimes even be required.
> > If weak refs suddenly make our memory model unsound, then the whole idea
> > of weak references stands to discussion.
>
> I don't think they make the memory model unsound per say.  But it is
> an issue, and one avoided by MIT Scheme by simply making it impossible
> to revive collected objects.  This thread made me consider what to do
> with such weak references and I decided that we should clear references
> to finalizable (which may already be finalized) objects.
>
> Attached is a patch to ensure that "live" weak references don't hold
> onto objects that may have been processed by a finalizer.  I think this
> removes the worst potential use-after-free footguns.
>

I'm not very comfortable with this change. This feels like trading in
one inconsistency (weak refs being cleared for a potentially non-dead
object) for another (potentially inconsistent ties of GC-controlled
memory to non-GC'd resources). Weak pairs and finalization already
undermine the strict regime that automatic memory management normally
provides, both intended to give the user more control, effectively
allowing "manual" management of external resources or limiting the
"liveness" of an object.

The potential use-after-free scenario can still happen if the object is
kept alive, regardless of how we handle weak refs, this is unavoidable
if we allow finalizers and keep the possibility of resurrection.

Clearing weak refs to objects that may not be dead after all seems to
me as the more confusing behaviour, an intendedly kept inconsistency
the user has no way to avoid without truly letting go of the object.


cheers
felix




[PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events)

2023-07-07 Thread felix . winkelmann
> While working on this, I also noticed a remaining bug in the weak locative
> handling: when a locative has already been cleared, it contains a NULL
> pointer, and we need to avoid dereferencing it on the next GC.

Thanks, pushed.


felix




[PATCH] Bugfix and drop weak references to finalizable objects (was: Re: [PATCH] thread-safe handling of asynchronous events)

2023-07-07 Thread Peter Bex
On Thu, Jul 06, 2023 at 09:05:03PM +0200, felix.winkelm...@bevuta.com wrote:
> > This would be problematic if the finalizer has run and deleted the
> > foreign object, while there are still weak references that hold onto
> > the object.  This has then become invalid/inconsistent.
> 
> I don't understand this, I'm afraid. Finalizers can always "revive"
> objects, this can't be avoided and may sometimes even be required.
> If weak refs suddenly make our memory model unsound, then the whole idea
> of weak references stands to discussion.

I don't think they make the memory model unsound per say.  But it is
an issue, and one avoided by MIT Scheme by simply making it impossible
to revive collected objects.  This thread made me consider what to do
with such weak references and I decided that we should clear references
to finalizable (which may already be finalized) objects.

Attached is a patch to ensure that "live" weak references don't hold
onto objects that may have been processed by a finalizer.  I think this
removes the worst potential use-after-free footguns.

NOTE: This issue already exists in older CHICKEN versions with weak
locatives, it is not newly introduced by weak pairs!

While working on this, I also noticed a remaining bug in the weak locative
handling: when a locative has already been cleared, it contains a NULL
pointer, and we need to avoid dereferencing it on the next GC.

Cheers,
Peter
From e9a267e6a1268f8f7eafdcca8b609c443d717e67 Mon Sep 17 00:00:00 2001
From: Peter Bex 
Date: Fri, 7 Jul 2023 11:07:43 +0200
Subject: [PATCH 1/2] Skip weak locatives that were already invalidated

This avoids a NULL pointer dereference
---
 runtime.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/runtime.c b/runtime.c
index edda5377..fbce94fd 100644
--- a/runtime.c
+++ b/runtime.c
@@ -4126,6 +4126,7 @@ static C_regparm void C_fcall update_locatives(int mode)
(mode == GC_REALLOC && !C_in_stackp(loc) && !C_in_heapp(loc))); /* 
NB: *old* heap! */
 
 ptr = C_block_item(loc, 0); /* fix up ptr */
+if (ptr == 0) continue; /* Skip already dropped weak locatives */
 offset = C_unfix(C_block_item(loc, 1));
 obj = ptr - offset;
 
-- 
2.40.1

From 9d1d1f3ee1284ba9e298d5dacdb77e8a3ad5fd27 Mon Sep 17 00:00:00 2001
From: Peter Bex 
Date: Fri, 7 Jul 2023 11:45:51 +0200
Subject: [PATCH 2/2] Don't retain weak references to finalizable objects

When an object would be garbage, but has a finalizer, it will be
artificially kept alive at the end of a GC cycle so that the finalizer
gets a chance to run, with the original event as an argument.

However, this object is effectively supposed to be garbage, and the
finalizer may put the object in a defunct state.  This means we don't
want code to be able to extract such an object through a weak
reference, because it should be considered garbage and *might* be
already cleaned up.  This could be a potential footgun, making use
after free bugs more likely.

To fix this, we make these "garbage" objects appear as garbage for the
purpose of clearing weak pointers.

The implementation is relatively trivial - we simply remember the heap
top pointer before we revive garbage objects that have finalizers, and
when dereferencing the pointer to the new object's location, where we
check that the object is in the target heap, we also check that it's
not in the portion of the heap where we've put the finalizable
objects and anything they reference.
---
 manual/Module (chicken gc)  |   6 ++
 runtime.c   |  38 +++-
 tests/weak-pointer-test.scm | 113 
 3 files changed, 141 insertions(+), 16 deletions(-)

diff --git a/manual/Module (chicken gc) b/manual/Module (chicken gc)
index 48653e3a..25a29373 100644
--- a/manual/Module (chicken gc)
+++ b/manual/Module (chicken gc)
@@ -53,6 +53,12 @@ Multiple finalizers can be registered for the same object. 
The order
 in which the finalizers run is undefined. Execution of finalizers
 may be nested.
 
+NOTE: When a finalizable object has any weak references (i.e., weak
+locatives or weak pairs) to objects that are only reachable through it
+or other finalizable objects, those references will be broken like
+when the objects had already been collected.  This is done in order to
+avoid user code from accessing objects that are possibly in an
+invalid state.
 
 === force-finalizers
 
diff --git a/runtime.c b/runtime.c
index fbce94fd..6fdd7fca 100644
--- a/runtime.c
+++ b/runtime.c
@@ -543,8 +543,8 @@ static void C_fcall mark_nested_objects(C_byte 
*heap_scan_top, C_byte *tgt_space
 static void C_fcall mark_live_objects(C_byte *tgt_space_start, C_byte 
**tgt_space_top, C_byte *tgt_space_limit) C_regparm;
 static void C_fcall mark_live_heap_only_objects(C_byte *tgt_space_start, 
C_byte **tgt_space_top, C_byte *tgt_space_limit) C_regparm;
 static C_word C_fcall intern0(C_char *name) C_regparm;
-static void C_fcall update_weak_pairs(int mode) C_regparm;
-static void 

Re: [PATCH] thread-safe handling of asynchronous events

2023-07-06 Thread felix . winkelmann
> On Wed, Jul 05, 2023 at 03:28:54PM +0200, felix.winkelm...@bevuta.com wrote:
> > The first patch provides the event-queue mechanism and cleans up the
> > scheduler a bit (hiding internal variables while also exposing ##sys#...
> > procedures to access them). This also defines hooks that a threading
> > API should override to allow suspension/resumption on events.
>
> I'm not super happy with exposing ##sys#fd-list and ##sys#timeout-list as
> a procedure under the exact same name that originally held a list.
> If there's any code that uses it, that would lead to strange errors.
> Might be better to either not expose it at all, or use a completely
> different name.
>

Here new patches for the scheduler and srfi-18 changes, with the exposed
accessors having now lame but concistent names.


felix
From ae180fc08f8a63edb823c5cbf94de682c10ef602 Mon Sep 17 00:00:00 2001
From: felix 
Date: Thu, 6 Jul 2023 21:22:18 +0200
Subject: [PATCH] add internal event-queue mechanism and hooks for threading 
 API, expose accessors to internal task lists.

---
 library.scm   | 71 -
 scheduler.scm | 88 +++
 2 files changed, 116 insertions(+), 43 deletions(-)

diff --git a/library.scm b/library.scm
index b04b330a..989f421f 100644
--- a/library.scm
+++ b/library.scm
@@ -44,6 +44,7 @@
##sys#default-read-info-hook ##sys#infix-list-hook
##sys#sharp-number-hook ##sys#user-print-hook
##sys#user-interrupt-hook ##sys#windows-platform
+   ##sys#resume-thread-on-event ##sys#suspend-thread-on-event
##sys#schedule ##sys#features)
   (foreign-declare #<
@@ -152,7 +153,13 @@ signal_debug_event(C_word mode, C_word msg, C_word args)
   C_debugger(, 3, av);
   return C_SCHEME_UNDEFINED;
 }
-
+   
+static C_word C_i_sleep_until_interrupt(C_word secs)
+{
+   while(C_i_process_sleep(secs) == C_fix(-1) && errno == EINTR);
+   return C_SCHEME_UNDEFINED;
+}
+   
 #ifdef NO_DLOAD2
 # define HAVE_DLOAD 0
 #else
@@ -5738,6 +5745,68 @@ EOF
 (define (##sys#kill-other-threads thunk)
   (thunk))	 ; does nothing, will be modified by scheduler.scm
 
+;; these two procedures should redefined in thread APIs (e.g. srfi-18):
+(define (##sys#resume-thread-on-event t) #f)
+ 
+(define (##sys#suspend-thread-on-event t)
+  ;; wait until signal handler fires. If we are only waiting for a finalizer,
+  ;; then this will wait forever:
+  (##sys#sleep-until-interrupt))
+
+(define (##sys#sleep-until-interrupt)
+  (##core#inline "C_i_sleep_until_interrupt" 100)
+  (##sys#dispatch-interrupt (lambda _ #f)))
+
+  
+;;; event queues (for signals and finalizers)
+  
+(define (##sys#make-event-queue)
+  (##sys#make-structure 'event-queue 
+'() ; head
+'() ; tail
+#f)) ; suspended thread
+
+(define (##sys#add-event-to-queue! q e)
+  (let ((h (##sys#slot q 1))
+(t (##sys#slot q 2))
+(item (cons e '(
+(if (null? h)
+(##sys#setslot q 1 item)
+(##sys#setslot t 1 item))
+(##sys#setslot q 2 item)
+(let ((st (##sys#slot q 3))) ; thread suspended?
+  (when st
+(##sys#setslot q 3 #f)
+(##sys#resume-thread-on-event st)
+
+(define (##sys#get-next-event q)
+  (let ((st (##sys#slot q 3)))
+(and (not st)
+ (let ((h (##sys#slot q 1)))
+   (and (not (null? h))
+(let ((x (##sys#slot h 0))
+  (n (##sys#slot h 1)))
+  (##sys#setslot q 1 n)
+  (when (null? n) (##sys#setslot q 2 '()))
+  x))
+
+(define (##sys#wait-for-next-event q)
+  (let ((st (##sys#slot q 3)))
+(when st
+  (##sys#signal-hook #:runtime-error #f "event queue blocked" q))
+(let again ()
+  (let ((h (##sys#slot q 1)))
+(cond ((null? h)
+   (##sys#setslot q 3 ##sys#current-thread)
+   (##sys#suspend-thread-on-event ##sys#current-thread)
+   (again))
+  (else
+(let ((x (##sys#slot h 0))
+  (n (##sys#slot h 1)))
+  (##sys#setslot q 1 n)
+  (when (null? n) (##sys#setslot q 2 '()))
+  x)))
+  
 
 ;;; Sleeping:
 
diff --git a/scheduler.scm b/scheduler.scm
index cbada6fb..759db957 100644
--- a/scheduler.scm
+++ b/scheduler.scm
@@ -29,7 +29,7 @@
   (unit scheduler)
   (uses extras) ; for sprintf
   (disable-interrupts)
-  (hide ready-queue-head ready-queue-tail ##sys#timeout-list
+  (hide ready-queue-head ready-queue-tail timeout-list fd-list
 	##sys#update-thread-state-buffer ##sys#restore-thread-state-buffer
 	##sys#unblock-threads-for-i/o
 	;; This isn't hidden ATM to allow set!ing it as a hook/workaround
@@ -105,7 +105,7 @@ static int C_fdset_nfds;
 static struct pollfd *C_fdset_set = NULL;
 
 inline static int C_fd_ready(int fd, int pos, int what) {
-  assert(fd == 

Re: [PATCH] thread-safe handling of asynchronous events

2023-07-06 Thread felix . winkelmann
> Perhaps simply we can just return two values?  The first a polling
> procedure and the second a procedure to add new objects to the
> finalizer?  You can just receive the polling procedure in a
> single-value context and ignore the object-adder if you don't want
> to use it.

That was also my thought. The problem here is that having a single
finalizer proc makes it possible to finalize on that procedure as well.
Splitting it into consumer/adder now has two procs holding on to
the queue and both must be released.

>
> Alternatively, make-finalizer could return a new structure type that
> represents the finalizer.  It could just be a wrapper for the queue
> internally.  We'd have to add a getter and an adder procedure that
> accepts this new object type (and perhaps a predicate).

Also an idea. Currently I prefer the following approach. I somehow
like the elegance of having a single procedure instead of creating
yet another data type and the usual bunch of operators.

(make-finalizer OBJ ...)
as before, but returns a "decorated" proc.

(add-to-finalizer FPROC OBJ ...)
extract the decoration and add finalizers to the objects finalizable
via the already existing FPROC.

> > > Separating the collectable object from the context prevents the
> > > collected object from re-entering the live system through the
> > > finalization procedure (it may set! some variable to it, for example
> > > and that's not desirable).  Apparently, this makes ephemerons easier to
> > > implement.
> > > I found this through Taylor Campbell's comment on Andy Wingo's blog post:
> > > https://wingolog.org/archives/2022/10/31/ephemerons-and-finalizers
> >
> > I don't quite understand why many APIs are so afraid of retaining
> > the finalized object. The point is becoming aware of the object
> > being reclaimable. If it survives yet another GC cycle, so what?
>
> This would be problematic if the finalizer has run and deleted the
> foreign object, while there are still weak references that hold onto
> the object.  This has then become invalid/inconsistent.

I don't understand this, I'm afraid. Finalizers can always "revive"
objects, this can't be avoided and may sometimes even be required.
If weak refs suddenly make our memory model unsound, then the whole idea
of weak references stands to discussion.

>
> And vice versa, if there are weak references which are now broken, and
> the finalizer restores the object, this might cause different kinds of
> inconsistencies to arise.

The user is already fiddling with the consistency of the pointer
universe when using weak refs. I see no way to address this here without
removing weak refs or finalization.


felix




Re: [PATCH] thread-safe handling of asynchronous events

2023-07-06 Thread Peter Bex
On Thu, Jul 06, 2023 at 02:51:43PM +0200, felix.winkelm...@bevuta.com wrote:
> That is indeed a shortcoming of the API. I must say that I'm not
> too keen on the "guardian" approach, though, or your suggested hacky
> workaround. There would be too many meanings attached to the
> argument for the polling procedure.

I agree, which is why I called it hacky.

> I also don't like the MIT apporach, since our very basic
> "set-finalizer!" has worked fine so far (with the execution context
> being the main problem).

Yeah, MIT's is rather heavy-handed.

Perhaps simply we can just return two values?  The first a polling
procedure and the second a procedure to add new objects to the
finalizer?  You can just receive the polling procedure in a
single-value context and ignore the object-adder if you don't want
to use it.

Alternatively, make-finalizer could return a new structure type that
represents the finalizer.  It could just be a wrapper for the queue
internally.  We'd have to add a getter and an adder procedure that
accepts this new object type (and perhaps a predicate).

> > Separating the collectable object from the context prevents the
> > collected object from re-entering the live system through the
> > finalization procedure (it may set! some variable to it, for example
> > and that's not desirable).  Apparently, this makes ephemerons easier to
> > implement.
> > I found this through Taylor Campbell's comment on Andy Wingo's blog post:
> > https://wingolog.org/archives/2022/10/31/ephemerons-and-finalizers
> 
> I don't quite understand why many APIs are so afraid of retaining
> the finalized object. The point is becoming aware of the object
> being reclaimable. If it survives yet another GC cycle, so what?

This would be problematic if the finalizer has run and deleted the
foreign object, while there are still weak references that hold onto
the object.  This has then become invalid/inconsistent.

And vice versa, if there are weak references which are now broken, and
the finalizer restores the object, this might cause different kinds of
inconsistencies to arise.

> Unless we are severely memory-constrained and must at all costs release
> the object, I see no need in separating object and context for
> finalization.

I don't think this separation was ever about memory efficiency.

> Moreover, there may be cases when we may need full access
> to the object to perform our finalization action.

Yeah, hiding the object could be problematic if it holds onto
several foreign objects that all have to be cleaned up.

> > Now I think this API is rather heavy-handed, but perhaps we can settle
> > on some sort of middle ground.  I also think that maybe separating the
> > finalization object from the "context" is too late for us, unless we
> > decide to rework the set-finalizer! API as well for CHICKEN 6.
> 
> Indeed. I wouldn't want to touch the underlying machinery. it works
> pretty well, I think.

Agreed.


> [ event queue API ]

> That sounds good, and I understand the motivation but I don't want to
> touch the scheduler, to be honest. The UTF transition is already enough
> work as it is. The original subject is merely a question of API choice,
> let's not try to fix everything right now.

Like I said, just a brain dump.  Perhaps something to keep in mind for
CHICKEN 7.  But as we design a new API we'd do well to make such a
change possible without too many changes in user code.

> I will try to come up with something better, to allow adding objects
> to an existing finalizer.

Looking forward to it!

Cheers,
Peter


signature.asc
Description: PGP signature


Re: [PATCH] thread-safe handling of asynchronous events

2023-07-06 Thread felix . winkelmann
> On Wed, Jul 05, 2023 at 03:28:54PM +0200, felix.winkelm...@bevuta.com wrote:
> > The first patch provides the event-queue mechanism and cleans up the
> > scheduler a bit (hiding internal variables while also exposing ##sys#...
> > procedures to access them). This also defines hooks that a threading
> > API should override to allow suspension/resumption on events.
>
> I'm not super happy with exposing ##sys#fd-list and ##sys#timeout-list as
> a procedure under the exact same name that originally held a list.
> If there's any code that uses it, that would lead to strange errors.
> Might be better to either not expose it at all, or use a completely
> different name.

Yes, there is a potential for confusion. Different names might probably
be in order.

> > The second patch provides a new signal API: "make-signal-handler",
> > which creates a handler for one or more signals and "signal-ignore" and
> > "signal-default" (to ignore a signal or set the default disposition).
> > This is roughly modelled after the Racket API. The old "signal-handler"
> > and "set-signal-handler!" procedures have been deprecated.
> >
> > The third patch provides "make-finalizer", inspired by Chez' guardians,
> > but slightly different to allow blocking/non-blocking tests for
> > finalizations. The old API is still available, as often finalization
> > performs only very basic operations independent of the currently
> > executing context. Finalization procedures can be composed, as a
> > finalizer itself becomes subject to finalization once all associated
> > objects have been collected. Building guardians on top of this
> > should be straightforward, but the latter does not (to my knowledge)
> > allow blocking a thread until a finalizer triggers so I chose not
> > to implement this interface.
>
> This API looks nice, but as I understand it, make-finalizer requires
> all objects in each "pool" to be known when calling it.  AFAIK this
> means that when you are creating objects on-the-fly, you'd have to
> call make-finalizer N times, and when retrieving objects you'd have
> to poll N finalizer "pools", and therefore you can't block on all
> objects you'd want to collect.

That is indeed a shortcoming of the API. I must say that I'm not
too keen on the "guardian" approach, though, or your suggested hacky
workaround. There would be too many meanings attached to the
argument for the polling procedure. I also don't like the MIT
apporach, since our very basic "set-finalizer!" has worked fine so
far (with the execution context being the main problem).

> Separating the collectable object from the context prevents the
> collected object from re-entering the live system through the
> finalization procedure (it may set! some variable to it, for example
> and that's not desirable).  Apparently, this makes ephemerons easier to
> implement.
> I found this through Taylor Campbell's comment on Andy Wingo's blog post:
> https://wingolog.org/archives/2022/10/31/ephemerons-and-finalizers

I don't quite understand why many APIs are so afraid of retaining
the finalized object. The point is becoming aware of the object
being reclaimable. If it survives yet another GC cycle, so what?
Unless we are severely memory-constrained and must at all costs release
the object, I see no need in separating object and context for
finalization. Moreover, there may be cases when we may need full access
to the object to perform our finalization action.

> Now I think this API is rather heavy-handed, but perhaps we can settle
> on some sort of middle ground.  I also think that maybe separating the
> finalization object from the "context" is too late for us, unless we
> decide to rework the set-finalizer! API as well for CHICKEN 6.

Indeed. I wouldn't want to touch the underlying machinery. it works
pretty well, I think.

> Perhaps another API that exposes the queues directly might be better.
> This way one could use one queue for different things, so that
> finalization, signals and file descriptor readiness could happen on
> the same queue, so that a thread could block on "any event".  A bit
> like the waitForMultipleObjects API in Windows.
>
> Something like:
>
> (make-event-queue) => q
>
> (enqueue-read q PORT)  ;; and a low-level (enqueue-read/fd q FD)?
> (enqueue-write q PORT)
> (enqueue-i/o q PORT)   ;; read or write
> (enqueue-finalization! q OBJ)
>
> (wait-for-event! q)  ;; blocks for any "enqueued" event
> (peek-event q)   ;; returns #f if nothing ready
>
> This makes it easy to pick off events from a queue, regardless of the
> type and you'll get an object of the type.  I don't know if it can be
> done easily, but it would allow GUI libraries to hook into this too
> by having an event loop provide events into a queue that a Scheme
> thread waits for.

That sounds good, and I understand the motivation but I don't want to
touch the scheduler, to be honest. The UTF transition is already enough
work as it is. The original subject is merely a question of API 

Re: [PATCH] thread-safe handling of asynchronous events

2023-07-06 Thread Peter Bex
On Wed, Jul 05, 2023 at 03:28:54PM +0200, felix.winkelm...@bevuta.com wrote:
> The first patch provides the event-queue mechanism and cleans up the
> scheduler a bit (hiding internal variables while also exposing ##sys#...
> procedures to access them). This also defines hooks that a threading
> API should override to allow suspension/resumption on events.

I'm not super happy with exposing ##sys#fd-list and ##sys#timeout-list as
a procedure under the exact same name that originally held a list.
If there's any code that uses it, that would lead to strange errors.
Might be better to either not expose it at all, or use a completely
different name.

Perhaps now is a good time to make the scheduler into a proper module
that exposes procedures in an unprefixed way?

> The second patch provides a new signal API: "make-signal-handler",
> which creates a handler for one or more signals and "signal-ignore" and
> "signal-default" (to ignore a signal or set the default disposition).
> This is roughly modelled after the Racket API. The old "signal-handler"
> and "set-signal-handler!" procedures have been deprecated.
> 
> The third patch provides "make-finalizer", inspired by Chez' guardians,
> but slightly different to allow blocking/non-blocking tests for
> finalizations. The old API is still available, as often finalization
> performs only very basic operations independent of the currently
> executing context. Finalization procedures can be composed, as a
> finalizer itself becomes subject to finalization once all associated
> objects have been collected. Building guardians on top of this
> should be straightforward, but the latter does not (to my knowledge)
> allow blocking a thread until a finalizer triggers so I chose not
> to implement this interface.

This API looks nice, but as I understand it, make-finalizer requires
all objects in each "pool" to be known when calling it.  AFAIK this
means that when you are creating objects on-the-fly, you'd have to
call make-finalizer N times, and when retrieving objects you'd have
to poll N finalizer "pools", and therefore you can't block on all
objects you'd want to collect.

So what's missing is a way to add an object to an existing finalizer.
Note that this is what guardians support too - when you call a
guardian procedure with an argument, that object is added to the pool.

A (somewhat hacky) way to do this would be to have the finalizer closure
check whether the "mode" argument is a boolean.  If "mode" is not a
boolean, it is assumed to be an object to add to the queue.
Perhaps with an extra check that it's not an immediate value.

Alternatively, we could steal (part of) MIT Scheme's (undocumented)
finalizer API:

https://git.savannah.gnu.org/cgit/mit-scheme.git/tree/src/runtime/gcfinal.scm?id=176cd871bdd9c9dabcb8ad602da0b618be2d0373#n36

There, you call (make-gc-finalizer) with the finalization procedure,
a predicate for the type of object that may be collected, a procedure
that can be used to extract the "context" from the collectable object
and a procedure that can be used to set the context inside the
collectable object.  The "context" here is the value that gets passed
to the finalizer procedure (typically, this would be the foreign pointer
inside a finalizable struct object).

Then, add-to-gc-finalizer! can be used to add objects to the finalizer
and remove-from-gc-finalizer! can be used to remove objects from the
finalizer.

Separating the collectable object from the context prevents the
collected object from re-entering the live system through the
finalization procedure (it may set! some variable to it, for example
and that's not desirable).  Apparently, this makes ephemerons easier to
implement.
I found this through Taylor Campbell's comment on Andy Wingo's blog post:
https://wingolog.org/archives/2022/10/31/ephemerons-and-finalizers

Now I think this API is rather heavy-handed, but perhaps we can settle
on some sort of middle ground.  I also think that maybe separating the
finalization object from the "context" is too late for us, unless we
decide to rework the set-finalizer! API as well for CHICKEN 6.

Perhaps another API that exposes the queues directly might be better.
This way one could use one queue for different things, so that
finalization, signals and file descriptor readiness could happen on
the same queue, so that a thread could block on "any event".  A bit
like the waitForMultipleObjects API in Windows.

Something like:

(make-event-queue) => q

(enqueue-read q PORT)  ;; and a low-level (enqueue-read/fd q FD)?
(enqueue-write q PORT)
(enqueue-i/o q PORT)   ;; read or write
(enqueue-finalization! q OBJ)

(wait-for-event! q)  ;; blocks for any "enqueued" event
(peek-event q)   ;; returns #f if nothing ready

This makes it easy to pick off events from a queue, regardless of the
type and you'll get an object of the type.  I don't know if it can be
done easily, but it would allow GUI libraries to hook into this too
by having an event loop provide 

[PATCH] thread-safe handling of asynchronous events

2023-07-05 Thread felix . winkelmann
Hello!

Currently we have no thread-safe way of handling asynchronous events
like POSIX signals and finalization. In both situations, the signal
handler and finalization procedures are called in whatever thread
is currently executing, which is a source of potential deadlocks
in a multithreaded environment.

One approach would be to have a dedicated thread for these events,
but after studying existing APIs like Chez' "guardians" (for finalization)
and the Racket signal interface, a "pull" interface that lets the
user ask explicitly for such events appears to provide maximum control
and still can be integrated into a threaded environment with relative
ease.

Therefore I provide 4 patches that implement an internal "event-queue"
mechanism and revised finalization and signal API based on these queues.
The basic principle is to associate a handler procedure with a queue
of asynchronous events. Calling the handler retrieves events from the
queue, either non-blocking or blocking. These events are not associated
with a thread, but with a procedure, allowing the separation of the
points in time when the event is triggered and the moment it is handled.

The first patch provides the event-queue mechanism and cleans up the
scheduler a bit (hiding internal variables while also exposing ##sys#...
procedures to access them). This also defines hooks that a threading
API should override to allow suspension/resumption on events.

The second patch provides a new signal API: "make-signal-handler",
which creates a handler for one or more signals and "signal-ignore" and
"signal-default" (to ignore a signal or set the default disposition).
This is roughly modelled after the Racket API. The old "signal-handler"
and "set-signal-handler!" procedures have been deprecated.

The third patch provides "make-finalizer", inspired by Chez' guardians,
but slightly different to allow blocking/non-blocking tests for
finalizations. The old API is still available, as often finalization
performs only very basic operations independent of the currently
executing context. Finalization procedures can be composed, as a
finalizer itself becomes subject to finalization once all associated
objects have been collected. Building guardians on top of this
should be straightforward, but the latter does not (to my knowledge)
allow blocking a thread until a finalizer triggers so I chose not
to implement this interface.

The fourth patch extends srfi-18 to set the hooks defined in
the first patch. This patch uses some definitions from the
scheduler.scm changes but does not directly depend on them (the hooks
will never be invoked).

Note that the basic C/Scheme glue for finalizers and signals has not
been touched, these changes merely provide a different interface. The
scheduler cleanup is only superficial and mostly for exposing whether
threads are ready or waiting and to provide some syntactic consistency.

I tried my best to split these changes into meaningful patches, but
my relation to git is a troubled one, and the changes to NEWS are
likely to result in conflicts anyway, so expect some fiddling.


cheers,
felix

From 2374d7653f6aca7b3d28e84a6338899a424ff2ed Mon Sep 17 00:00:00 2001
From: felix 
Date: Wed, 5 Jul 2023 14:58:24 +0200
Subject: [PATCH] add internal event-queue mechanism and hooks for  threading
 API

---
 library.scm   | 73 --
 scheduler.scm | 88 +++
 2 files changed, 117 insertions(+), 44 deletions(-)

diff --git a/library.scm b/library.scm
index b04b330a..a5a01853 100644
--- a/library.scm
+++ b/library.scm
@@ -31,7 +31,7 @@
   (disable-interrupts)
   (hide ##sys#dynamic-unwind
 	##sys#vector-resize ##sys#default-parameter-vector 
-	current-print-length setter-tag
+	current-print-length setter-tag 
 	##sys#print-exit
 	##sys#format-here-doc-warning
 	exit-in-progress cleanup-before-exit chicken.base#cleanup-tasks
@@ -44,6 +44,7 @@
##sys#default-read-info-hook ##sys#infix-list-hook
##sys#sharp-number-hook ##sys#user-print-hook
##sys#user-interrupt-hook ##sys#windows-platform
+   ##sys#resume-thread-on-event ##sys#suspend-thread-on-event
##sys#schedule ##sys#features)
   (foreign-declare #<
@@ -152,7 +153,13 @@ signal_debug_event(C_word mode, C_word msg, C_word args)
   C_debugger(, 3, av);
   return C_SCHEME_UNDEFINED;
 }
-
+   
+static C_word C_i_sleep_until_interrupt(C_word secs)
+{
+   while(C_i_process_sleep(secs) == C_fix(-1) && errno == EINTR);
+   return C_SCHEME_UNDEFINED;
+}
+   
 #ifdef NO_DLOAD2
 # define HAVE_DLOAD 0
 #else
@@ -5738,6 +5745,68 @@ EOF
 (define (##sys#kill-other-threads thunk)
   (thunk))	 ; does nothing, will be modified by scheduler.scm
 
+;; these two procedures should redefined in thread APIs (e.g. srfi-18):
+(define (##sys#resume-thread-on-event t) #f)
+ 
+(define (##sys#suspend-thread-on-event t)
+  ;; wait until signal handler fires. If we are only