Conversation
|
|
|
Allow me summarize my understanding of this work (feel free to correct me):
This strikes me as a good first step to get an initial "memory cleanup at exit" mode working in OCaml 5. Footnotes
|
|
Your understanding is correct. |
|
Possibly I am misunderstanding something here. In the current runtime, when a domain terminates, its major heap (a.k.a. "shared heap") is "orphaned" by |
|
(This PR is related to #13010 and I was not sure which one to comment in.) Here is the behavior I would expect for
In 4.x, the cleanup-at-exit mode would make I could see a use-case for another function that does a "soft shutdown" by raising the |
|
This new version incorporates @NickBarnes's feedback, but should not be considered ready yet. There are still issues when using the debug runtime. On the other hand it will be easier to review as the cleanup code paths are more visible now. |
|
Status update:
|
| int i = atomic_load_acquire(&caml_num_domains_running) + 5; | ||
| while (!caml_domain_alone() && i != 0) { | ||
| #ifdef WIN32 | ||
| Sleep(1); |
There was a problem hiding this comment.
This sleep-based synchronization looks very fishy to me -- but then I'm not a domain expert. Is there not a standard synchronization primitive that we can use?
There was a problem hiding this comment.
It's only ugly (-:
If we reach this state, there is absolutely no guarantee that the interrupt request will be processed by the domains, so we can not wait for them without an upper time bound, in case at least one domain is unresponsive. The time limit here is (number of currently running domains + 5) x 1 millisecond. That's an arbitrary number which hopefully allows every non-sleeping domain to be scheduled and receive the exit request.
There was a problem hiding this comment.
I understand the concern, but I am not sure what scenarios you have in mind about delayed processing.
I am assuming that all domains have at least a thread that checks for interruptions on a regular basis -- otherwise this is a bug that should be fixed to restore this property, because the minor GC relies on it. I am not sure if you make a different assumption, or if you also assume this but then are thinking of the other threads on that same domain that are waiting on other blocking calls, or something else?
As long as the owner of the domain lock regularly handles interruptions, we know (I think?) that the other threads on the domain will not use the runtime again before they take the domain lock. So we could have a check for "we are forced to terminate" at that point that would shut them down before they return to OCaml code, and not have to wait at all for those threads right now.
|
The implementation mechanism for interrupting all domains in this PR is somewhat custom: it uses the same interruption mechanism as STW sections, but it is not implemented as a STW itself, instead it is a new code path in I find it hard to reason about, and in particular there are two subtleties that may or may not be handled properly:
I think that a robust solution should come with an explanation of its interaction with the other domain lifetime events (see for example the existing documentation comments about the backup thread cycle above If I was trying to implement this, I would try the following:
|
An earlier work (#13010) did use an STW for convenience, and some people commented this was probably overkill. But I agree that it was simpler to write as an STW rendezvous.
If a spawn was ongoing and had already passed the added test, then it will spawn correctly, and then honour the exit request (almost) immediately.
Argh, this is something I did not take into account. The interrupt handling needs to ignore the exit request if processed in the backup thread, i.e. if |
Ah, I suppose you had comment #13010 (comment) in mind. It is possible that I am contradicting myself from 2 months ago, but I think that (1) notifying other domains that they should terminate (as we discussed then) should not require a STW (and I don't think that raising an exception from within a STW is currently supported), but that (2) cleaning up the runtime state shared by all domains (as discussed now) requires a synchronization with all mutators, which could be done with a STW. In any case, "simpler" is good here, as long as it aligns with "reasonably easy to convince ourselves that it is correct". |
|
Aside: I thought of trying to implement a domain-safe |
You are not contradicting yourself - your opinion on the problem evolves as you are getting more familiar with it. |
There are several tests in the existing testsuite which, in their current state, exit with multiple domains running, and which are representative enough:
|
|
The PR has been marked as ready for review. @dustanddreams, can you summarise what has improved since it was set to draft a few weeks ago (#12964 (comment))? Is the original PR message still an accurate summary of the guarantees provided? #12964 (comment). |
|
I have updated the PR description to match the current reality. Short summary: when cleanup mode is enabled, memory is released only if only one domain is left running at program shutdown, otherwise the leftover threads are cancelled but nothing else is done for we can't assume anything about the state they are in. When cleanup mode is not enabled, there is no change of behaviour at all. |
|
Note that changes are to be expected after #13272 goes in, since there will be more dynamic allocations to clean up. If you're short on time, I'd suggesting postponing your review until then. |
|
I don't think I will be able to do a full review soon, but I had a quick look at it seems that the PR is moving in the direction of being simpler, thanks! On the other hand, I remain unconvinced by the reasoning in Here is my intuition about this -- but it may very well be incorrect/simplistic.
If I was writing this myself, I would try to ensure that when a non-backup thread regains the domain lock, it starts by checking whether the domain has already been shut down, and terminating itself in this case. (Can we check this just by looking at |
|
@dustanddreams and I discussed on a different channel and decided that running the leak sanitizer tests in the CI should be considered in a different PR. I've made an issue to track this: #13515. |
|
(I don't have the bandwidth to look at this right now, so please feel free to make a decision without me if you can.) |
|
@damiendoligez, IIUC, you have self-requested to review this PR. Do you plan to have a look? |
|
@dustanddreams the failing See related discussions at #13407 (comment). |
Doesn't look like a new data race. I've proposed a fix in #13529. |
Make the value of the backup_thread_running per-domain field reliable.
Upon shutdown, if memory cleanup is requested, make sure all domain and backup threads are terminated, and invoke the domain termination function for the last running domain if it was alone at shutdown time.
Upon shutdown, if memory cleanup is requested, release shared heap memory.
|
Thanks @kayceesrk for your work on the runtime event races. |
|
I believe that the PR is ready. The test failures in CI runs have been fixed in #13529. Since we haven't heard from @damiendoligez, I'll go ahead and merge. Thanks for the work and persevering through the various changes in the runtime @dustanddreams. |
Memory cleanup at exit (cherry picked from commit 62b7a2d)
Memory cleanup at exit (cherry picked from commit 62b7a2d)
This is a
work-in-progresssupport of "memory cleanup at exit" code (OCAMLRUNPARAM=c), see issue #10865.This is implemented in two parts:
domain_terminatedoes in the main thread upon exit.This appears to work when only one domain is left running at program termination time. When multiple domains are used, no effort to release memory is achieved due to the domains being in an unknown state. However, if memory cleanup at exit mode is enabled, the domain threads will be forcibly stopped.