-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Equivalent programs show different behaviour #508
Comments
Using
whether using I'm not sure why using
there is no guarantee that all finalizers will be run. In particular, it admits the following sequence of actions:
However, changing
does not change the behavior of the program (no |
The original example does not use
There is a while-loop in the
Good point. The handler cannot rely on the result of
I think this would lose finalizers as you observed in a message to MLton-user. It would be possible to apply a similar fix but this becomes quite a lot of code in the original example because there are 'finalizer contexts' which involves a set of pending lists, once for each context. I have tried a different approach, using a boolean notifier, to ensure that
The updated example is attached.
Also, the updated example does not change the behavior (no |
I was responding to the following from the original post:
I misunderstood what was meant by "empties the list of pending finalizers". I took it to mean "the loop that keeps running finalizers", but I realize now that it means "setting
Agreed, though it shouldn't make a difference in this example, because no finalizers are created by finalizers.
I haven't looked at the updated example in detail, but agreed that is a strange behavior that no |
These finalize functions are expected to return false only if no cleaning occurred between evaluation of the `doGC` function argument and the function returning, to indicate that further GC would not find any other unreachable finalizable values, but this was not guaranteed [1]: after one of these finalize functions evaluated its `doGC` function argument, another thread (Poly/ML) or handler (MLton) could clean some pending lists so the finalize function performed no further cleaning and returned false, even though further GC could make more finalizable values reachable, enabling more finalizers to be run. This issue is fixed by introducing a list of notifiers with each pending list. A notifier is a `bool ref`. When a pending list is cleaned, its notifiers are all set to true. A finalize function adds a notifier before evaluating `doGC` and removes the notifier after cleaning, returning the notifier value. Therefore a finalize function is guaranteed to detect any cleaning between evaluation of the `doGC` function argument and the returning. Note that cleaning outside this window may be detected causing the finalize function to return true when it could return false but this does not matter and is unlikely. Another issue with these functions is that they caused the next round of cleaning by the GC handler to be skipped, to avoid double-cleaning, but the `doGC` function argument may not invoke GC: it could simply be the identity function. Therefore cleaning after a subsequent GC would not occur when it should. The issue is avoided by using a different method to reduce the likelihood of double-cleaning: cleaning in the GC handler cleans a pending list only if it has no notifiers. 1. As pointed out by @MatthewFluet MLton/mlton#508 (comment)
I have an example where a finalizer is unexpectedly not being run. However, the finalizer is run if I transform the code to something (that I thought was) equivalent. This is using MLton 20210117.
A highly reduced example is attached which is built with the command
make mlton
. The code contains a function declarationand two references to the function of the form
theFunction self
. When run, it produces the outputso
obj2
is not finalized. If the code is changed to declareand the two references to the function changed to
theFunction (#n self)
, then it produces the outputso
obj2
is finalized. Therefore, finalization ofobj2
depends on whether the record componentn
is selected by pattern matching in the function declaration or in the argument of each call.The reason that finalization does not occur in the original example is probably due to my alternative implementation of
FINALIZABLE
in the structureFinalizable
(which is based on my Poly/ML implementation). At exit, the finalizers are cleaned up by code registered with bothMLtonSignal.handleGC
andOS.Process.atExit
. There is no code that empties the list of pending finalizers at exit as the in the MLton implementation. Instead, data races are avoided by using the moduleSharedVar
which protects access to a shared variable usingMLton.Thread.atomically
. It can be seen that this protection has an effect for the list of pending finalizers in this example: in the original example, changingto
i.e. removing the protection from the module
SharedVar
produces the outputPossibly I am misusing
MLton.Thread.atomically
or have done something wrong. Still, I am surprised that two seemingly equivalent programs can give different results.The text was updated successfully, but these errors were encountered: