MPR#7796: avoid running at_exit functions twice#1783
MPR#7796: avoid running at_exit functions twice#1783xavierleroy wants to merge 3 commits intoocaml:trunkfrom
Conversation
In particular when the runtime is in cleanup-at-exit mode. Other cases of multiple execution of at-exit functions remain, e.g. MPR#7253.
stdlib/stdlib.ml
Outdated
| let do_at_exit () = | ||
| (!exit_function) (); | ||
| (* MPR#7796: make sure these at-exit functions will not be run again *) | ||
| exit_function := (fun () -> ()) |
There was a problem hiding this comment.
Can it happen that !exit_function runs at_exit several times and then do_at_exit again? In this case, with your definition, I think you will get repetitions, while
let f = !exit_function in
exit_function := (fun () -> ());
f ()does not.
|
One could also guard against repeated execution of the individual at-exit functions, as in: diff --git a/stdlib/stdlib.ml b/stdlib/stdlib.ml
index f87787b9d4..d59bb8b170 100644
--- a/stdlib/stdlib.ml
+++ b/stdlib/stdlib.ml
@@ -538,6 +538,8 @@ let exit_function = ref flush_all
let at_exit f =
let g = !exit_function in
+ let already_ran = ref false in
+ let f () = if !already_ran then () else (already_ran := true; f ()) in
exit_function := (fun () -> f(); g())
let do_at_exit () = (!exit_function) ()I think this should also fix the cases of repeated execution reported in MPR#7253. |
|
Wow. A very good catch! The PR is a clear improvement as it is, but @gasche's version (with erasing |
Nicer implementation of do_at_exit contributed by @gasche.
|
I don't think @gasche's scenario should happen in practice, but his alternate implementation is nicer, so I just commited it to this GPR. |
|
Doesn't this second version create a race condition, if the change |
|
There are also race conditions in @xavierleroy's and @nojb's version, right? |
|
I think the first version is not worse than what we had before w.r.t to |
|
Replicating a few lines from |
|
Could a context switch happen when a closure is allocated? let at_exit f =
let g = !exit_function in
exit_function := (fun () -> f(); g()) |
|
Yes. |
Update the otherlibs/threads version of the stdlib.
|
Thank you @xclerc for having obliquely reminded me to update the otherlibs/threads stdlib. otherlibs/threads is the VM threads, where preemption occurs only at function calls and loops. So, reading a reference then assigning the reference is actually atomic, making the For system threads |
But, precisely, preemption could occur after reading and setting
Sure; I guess my concern was with the composition of libraries. |
|
OK, I thought you were concerned about the (non-) atomicity of swapping the contents of the reference. Your scenario for a race condition can occur both with VM threads and system threads, I believe. All this being said, I'd like to improve the sequential case (which this PR does) before worrying about thread safety. Can I please have permission to merge? |
gasche
left a comment
There was a problem hiding this comment.
Approved. I'll let @xavierleroy merge and cherry-pick.
|
The potential for I agree with @xclerc that it does feel strange to introduce a thread safety issue in a feature where it's very difficult (indeed, impossible in the general case) to work around it in user code. |
|
(It occurred to me that an alternative would be to only change |
|
Having only idempotent functions registered through |
|
Well, the idea is just to put the burden on the user of |
|
Cent fois sur le métier... I'll be back with a different implementation. |
|
I am back! See #1790. @nojb's suggestion above (#1783 (comment)) works great. |
* add missing 'watch:' in watch scraper, less logging in planet scraper * pin river to b990f702d29c7a7139920d701154e9db595eae1f
In particular when the runtime is in cleanup-at-exit mode. Fixes MPR#7796
Other cases of multiple execution of at-exit functions remain, e.g. MPR#7253. See also #685.