Skip to content

Resource-safe C interface, part 0#9037

Merged
gasche merged 4 commits intoocaml:trunkfrom
gadmm:resource-safe-api-pt0
Oct 16, 2019
Merged

Resource-safe C interface, part 0#9037
gasche merged 4 commits intoocaml:trunkfrom
gadmm:resource-safe-api-pt0

Conversation

@gadmm
Copy link
Copy Markdown
Contributor

@gadmm gadmm commented Oct 11, 2019

This is the first part of #8993, where I apply the semantics change for caml_check_urgent_gc agreed upon at #8691 (comment).

In 4.9, the public function caml_check_urgent_gc checks for finalisers, but not other callbacks. In 4.10, the check for finalisers is removed from allocation functions called from C code at #8691. At the same time, execution of all pending callbacks is added to caml_check_urgent_gc, and several new polling points are added. I think the motivation is to ensure, in many places, that memprof callbacks are called as soon as possible after an allocation.

There is a desire to build on #8691 to make the C API safe in the presence of asynchronous exceptions both for the runtime and the user (#8993, #8997). This requires: reducing the number of implicit polling points in C, and for functions that need to poll, replace them with _exn variants that return the exception, to be used to perform appropriate resource clean-up. But this is hard to use in practice if the functions that can raise asynchronous exceptions are many.

So, the current PR furthers #8691 by changing again caml_check_urgent_gc, such that it does not call any callback. We take advantage that in 4.9, it is not yet a reliable polling location. The new code is roughly equivalent to the OCaml 4.9 behaviour modulo fewer checks for finalisers. Any place where the check for finaliser could be relied on has been kept as a polling location.

Some polling points added explicitly with #8691 were in primitives, right before returning to OCaml. I removed them and propose a more robust strategy to ensure the memprof callback is called no later in those cases (not implemented in this PR): have non-noalloc C calls poll when returning (this should be easy to do during the work on better safe points). In addition, if in the future it is decided and enforced that memprof callbacks should not raise exceptions, it will also be possible to add more explicit calls to them independently of the possibly-raising callbacks.

Since memprof is not going to be ready for 4.10, there will still be some time to discuss the appropriate polling of memprof callbacks in the near future.

The first 3 clean-up commits have already been reviewed at #8993, they are included here to avoid me a tedious rebase.

This PR is targeted at 4.10 because it relies on the fact that polling points added in #8691 have not reached users yet, and also because #8993 is currently based on it (the current PR enabled a simpler public API at #8993).

(@jhjourdan, @stedolan)

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 12, 2019

@jhjourdan I can anticipate one remark: sometimes I call one set of callbacks without calling the other ones. This matches 4.9. An alternative is to always call all callbacks when I call some callbacks. This is debatable, let me know what you think.

Copy link
Copy Markdown
Contributor

@jhjourdan jhjourdan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this new proposal. This goes to the right direction, we are almost there! I have two concerns:

@jhjourdan I can anticipate one remark: sometimes I call one set of callbacks without calling the other ones. This matches 4.9. An alternative is to always call all callbacks when I call some callbacks. This is debatable, let me know what you think.

I would indeed prefer not calling these functions directly anywhere, except in caml_process_pending_callbacks.

Apart from that, I would prefer if we could use the polling versions everywhere it is possible in the OCaml runtime. This include functions like Array.make: they are not designed to be called from C code, so that they just correspond to allocation from OCaml code, which we decided were polling.

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 13, 2019

About the question about removed polling points in primitives that allocate (which you introduced in order to call memprof callbacks), and your request:

Apart from that, I would prefer if we could use the polling versions everywhere it is possible in the OCaml runtime. This include functions like Array.make: they are not designed to be called from C code, so that they just correspond to allocation from OCaml code, which we decided were polling.

So you were not convinced by my proposition to do it once and for all allocating primitives. This solution is not ready, but neither is memprof so I felt like there was no incentive to rush this into 4.10. But maybe you have other plans. I agree to add them (in the case of allocating primitives) and I trust you that it makes a real difference.

You are going to great lengths to call the memprof callback immediately after the allocation. If so, isn't this an information that the memprof callback could use, i.e. a boolean saying whether the call was immediate or delayed? That distinction could also justify adding such calls, in which case it could take the form of a specific function rather than the generic process_pending_callbacks. (It does not need to be implemented right now.)

I am done with my questions for this one!

@jhjourdan
Copy link
Copy Markdown
Contributor

So you were not convinced by my proposition to do it once and for all allocating primitives. This solution is not ready, but neither is memprof so I felt like there was no incentive to rush this into 4.10. But maybe you have other plans. I agree to add them (in the case of allocating primitives) and I trust you that it makes a real difference.

Ah, sorry, I don't remember this proposal (the discussions around these PRs really start being too long... and I am partly responsible for this, I know). Could you recall me your proposal?

Note, also, that many of these caml_check_urgent_gc calls follow calls to caml_initialize, so that it's a good thing to check for urgent GC, because of the many back-references.

You are going to great lengths to call the memprof callback immediately after the allocation. If so, isn't this an information that the memprof callback could use, i.e. a boolean saying whether the call was immediate or delayed? That distinction could also justify adding such calls, in which case it could take the form of a specific function rather than the generic process_pending_callbacks. (It does not need to be implemented right now.)

I'd be happy to add this Boolean, but it seems rather hard to compute to me. If you use a specific function for this, how do you distinguish in the memprof queue the callbacks that have just been added from the old ones? I'm open to discuss this, but let's focus for now on this PR. If you have ideas, I'd be happy if you give them in another channel.

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 13, 2019

Ah, sorry, I don't remember this proposal (the discussions around these PRs really start being too long... and I am partly responsible for this, I know). Could you recall me your proposal?

Have all non-noalloc C calls (from OCaml) poll when returning. This should be easy to implement as part of the work on better safe points. In addition, if in the future it is decided and enforced that memprof callbacks should not raise exceptions, it will also be possible to add more explicit calls to them independently of the possibly-raising callbacks.

Note, also, that many of these caml_check_urgent_gc calls follow calls to caml_initialize, so that it's a good thing to check for urgent GC, because of the many back-references.

I did not remove such calls (apart from the one discussed above which I did not interpret as such).

I'd be happy to add this Boolean, but it seems rather hard to compute to me. If you use a specific function for this, how do you distinguish in the memprof queue the callbacks that have just been added from the old ones? I'm open to discuss this, but let's focus for now on this PR. If you have ideas, I'd be happy if you give them in another channel.

The idea was to distinguish the most recent one, but I trust you if you say it is more complicated than that. (Future evolution can influence what I do now.)

Also, would you still be comfortable in calling caml_process_pending_callbacks instead of memprof callbacks if the order in which callbacks are called (memprof before anything else) was changed, or will this decision rigidify caml_process_pending_callbacks? (I was thinking that for latency in reaction to signals, it is better to do signals first).

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 13, 2019

Here's my current impression: I am not opposed to calling memprof callbacks directly in allocating primitives in addition to a systematic polling on return (which I think is going to be there in any case in the end), however it seems premature to decide the exact form it takes: a generic process_pending_callbacks that will check signals first, a generic call to memprof callbacks with an enforced non-raising behaviour, a specific call to memprof callbacks that knows it is called first thing after the allocation... I do not think that it is urgent to discuss and decide this before 4.10, so the default is to keep it as it was in 4.9.

(If you tell me they do make a difference for 4.10, I add them.)

@gadmm gadmm force-pushed the resource-safe-api-pt0 branch 2 times, most recently from e13511e to 7b02c13 Compare October 13, 2019 22:50
@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 13, 2019

The new last commit concerns what still has pending questions above. (new check_urgent_gcs & polling ASAP after major allocations).

For polling ASAP after major allocations, there is no one solution that satisfies all constraints so I came up with what I think is the best: call memprof callbacks directly. I will move the calls to signals handlers to first position in process_pending_callbacks in the other patch to fix a bug discovered there. This voids the certainty that the allocation just happened. In addition, calling process_pending_callbacks is likely going to be redundant at some point because non-noalloc C calls poll on return. Calling memprof callbacks directly instead is closer to your original intent of calling them ASAP and will keep making sense in the future. In addition it is the most flexible option which you can change anyway in the future.

The drawback is that it breaks the rule that all callbacks are executed via process_pending_callbacks (which was already broken in the case of memprof_handle_postponed).

In any case I did not take a decision there, its purpose is to continue the discussion and let you check that I took all other remarks into account. But I am satisfied with this one.

(Edit: except for the part where the first callback in memprof_handle_postponed will poll at the next allocation... I'll try something else.)

@jhjourdan
Copy link
Copy Markdown
Contributor

Have all non-noalloc C calls (from OCaml) poll when returning. This should be easy to implement as part of the work on better safe points. In addition, if in the future it is decided and enforced that memprof callbacks should not raise exceptions, it will also be possible to add more explicit calls to them independently of the possibly-raising callbacks.

Alright. I confirm this is a promising idea, but for now this is not obvious this will work, and will need at least a few benchmarks to check that performance is not affected too much. Said otherwise, I agree that would be a solution to this problem, but for now we don't have it. And it does not cost much to do manual polling instead for now, and revert that once automatic polling will be available.

however it seems premature to decide the exact form it takes: a generic process_pending_callbacks that will check signals first, a generic call to memprof callbacks with an enforced non-raising behaviour, a specific call to memprof callbacks that knows it is called first thing after the allocation... I do not think that it is urgent to discuss and decide this before 4.10, so the default is to keep it as it was in 4.9.

That's not only about 4.10. I'd like to have a version that works well in the trunk branch independently of how long the safe point change will take. I don't think it's so premature to doe this change, since we can revert it easily without creating incompatibilities.

@jhjourdan
Copy link
Copy Markdown
Contributor

For polling ASAP after major allocations, there is no one solution that satisfies all constraints

Which are the constraints? Is there a problem with polling signals there too?

This voids the certainty that the allocation just happened.

Actually, I don't think. What we want in memprof is that the callback gets called during the call of e.g., Array.make and not after. The user cannot observe whether the actual call to caml_alloc_shr happened before or after the signal handler.

In addition, calling process_pending_callbacks is likely going to be redundant at some point because non-noalloc C calls poll on return.

And at that point, my proposal is to remove that call. But only then.

@gadmm gadmm force-pushed the resource-safe-api-pt0 branch from 7b02c13 to 509b284 Compare October 14, 2019 13:07
@xavierleroy xavierleroy added this to the 4.10 milestone Oct 14, 2019
@gadmm gadmm force-pushed the resource-safe-api-pt0 branch from 509b284 to d19bfb5 Compare October 14, 2019 13:11
@jhjourdan
Copy link
Copy Markdown
Contributor

This new version seems good to me! I reviewed and added a few nitpicks, but that's ok to merge as-is.

@gasche
Copy link
Copy Markdown
Member

gasche commented Oct 14, 2019

I'll approve (on @jhjourdan's behalf; I haven't reviewed myself) and merge once @gadmm is okay with it (he may want to take care of the nitpicks first) and the CI is green. Probably tomorrow.

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 14, 2019

Alright, I was coming to the same conclusion after the failure of my idea of only calling memprof callbacks (which in addition you convinced me was useless), but your point about having trunk in good shape for experimenting convinced me it's the right approach. In the end we are very close to your original code (but avoid polling in sensitive places).

I'll turn to #8993 now and deal with the nitpicks in the meanwhile, so yes please wait for my greenlight.

the discussions around these PRs really start being too long...

I am responsible for this as well, I need to be sure I understand everything, and maybe some parts would have been easier to discuss in person. I could try to pass by Paris more often. Thanks for the patient replies.

@jhjourdan
Copy link
Copy Markdown
Contributor

The CI failure corresponds to the repetitive failures that we see on the Inria CI for a few days. I don't know what this is, but this is probably unrelated to this PR.

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 15, 2019

The CI failure corresponds to the repetitive failures that we see on the Inria CI for a few days. I don't know what this is, but this is probably unrelated to this PR.

Good to know. I was taking it personally!

gadmm added 3 commits October 15, 2019 14:02
This is dead code since removal of vmthreads.
…on)"

caml_raise_if_exception remains private; all public _exn functions
will be provided with a directly-raising variant to keep supporting current
programming styles.
@gasche
Copy link
Copy Markdown
Member

gasche commented Oct 15, 2019

I'll turn to #8993 now and deal with the nitpicks in the meanwhile, so yes please wait for my greenlight.

Note: I think it would be nice to merge this one soon, if it's ready to be merged, instead of trying to get both this one and #8993 perfect at the same time. You can always rebase #8993 on top of the final version of the current PR, and you can also do some changes in #8993 to the changes that were done here if everyone agrees. But keeping PRs open and in-flight has a cost (it introduces a risk of conflicts for other PRs, etc.), and I think we should try to polish and merge quickly when people have converged on a PR.

@jhjourdan
Copy link
Copy Markdown
Contributor

jhjourdan commented Oct 15, 2019

There is a conflict introduced by #8809. This is just a matter of renaming a caml_check_urgent_gc into caml_process_pending_callbacks.

@gadmm gadmm force-pushed the resource-safe-api-pt0 branch from a0f0d4b to 2a6d038 Compare October 15, 2019 14:59
@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 15, 2019

Just pushed one based on your latest suggestion. caml_process_pending_callbacks becomes caml_do_urgent_gc_and_callbacks. Indeed, we do not care if check_urgent_gc was done twice, and in the latest version of the code this would happen only in caml_garbage_collection. I think this is clearer indeed, and made possible because I now call signal handlers directly in enter/leave_blocking_section, and for the functions in gc_ctrl.c the change does not matter. In the end the distinction caml_do_urgent_gc_and_callbacks vs. caml_check_urgent_gc_and_callbacks reflects well the clarification we did during this PR.

Let me now have a look at your proposed patch.

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 15, 2019

I also added your bugfix from the other PR, thanks!

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 15, 2019

I have added the change to caml_make_array from your patch. Other than that it is almost identical. The difference is your change to enter/leave_blocking_section. I say that the fact that some signals do not interact at all with the GC is enough to treat them specially, and the fact that there is no need to "fix" the previous behaviour is enough to leave it as in 4.9. (The rule that all callbacks have the same entry point is already broken inside caml_memprof_track_young.)

@gadmm gadmm force-pushed the resource-safe-api-pt0 branch from 2a6d038 to a61ea89 Compare October 15, 2019 15:17
@jhjourdan
Copy link
Copy Markdown
Contributor

Ok, thanks! Let's wait for CI, and if it succeeds, then I think this is good to merge, and I no longer have any more remarks (I think? ;) ). The only thing that you will need to do is, of course, squashing your "fixup" commit.

@gadmm gadmm force-pushed the resource-safe-api-pt0 branch from a61ea89 to a0c82c7 Compare October 15, 2019 15:23
@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 15, 2019

Done! Thanks.

@gadmm gadmm force-pushed the resource-safe-api-pt0 branch from a0c82c7 to 5e57970 Compare October 15, 2019 15:39
Copy link
Copy Markdown
Member

@gasche gasche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(approval on @jhjourdan's behalf; I did not review the code myself)

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 15, 2019

There's still a bug to fix.

In 8691, caml_check_urgent_gc was merged with the function that runs
asynchronous callbacks. The rationale was that caml_check_urgent_gc
already runs finalisers, and so could have run any asynchronous
callbacks.

We agreed on a different strategy: we know that users could not rely
on asynchronous callbacks being called at this point, so take the
opportunity to make it callback-safe, like was done for allocation
functions.

The new check_urgent_gc no longer calls finalisers (nor any
callbacks), and instead two internal functions are introduced:

* caml_do_urgent_gc_and_callbacks : function to perform actions
  unconditionally.

* caml_check_urgent_gc_and_callbacks : function that checks for
  something to do, and then executes all actions (GC and callbacks).
@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 15, 2019

The following change which I forgot when deciding to directly call caml_process_pending_signals was causing a loop, because caml_something_to_do was never reset.

diff --git a/runtime/signals.c b/runtime/signals.c
index 7ccf60cdf..11a47e5ae 100644
--- a/runtime/signals.c
+++ b/runtime/signals.c
@@ -169,7 +169,7 @@ CAMLexport void caml_enter_blocking_section(void)
     caml_enter_blocking_section_hook ();
     /* Check again for pending signals.
        If none, done; otherwise, try again */
-    if (! caml_something_to_do) break;
+    if (! signals_are_pending) break;
     caml_leave_blocking_section_hook ();
   }
 }

@gadmm gadmm force-pushed the resource-safe-api-pt0 branch from 5e57970 to 2433b93 Compare October 15, 2019 17:29
@jhjourdan
Copy link
Copy Markdown
Contributor

The following change which I forgot when deciding to directly call caml_process_pending_signals was causing a loop, because caml_something_to_do was never reset.

Alright. Another fix would be to call caml_check_pending_signals_and_urgent_gc.

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 15, 2019

Is there any issue with this version?

@jhjourdan
Copy link
Copy Markdown
Contributor

Not really :D That's mostly a matter of respecting abstraction barriers: only one entry point for async callbacks.

@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 15, 2019

One way to see it is that the GC, memprof_track_young and enter/leave_blocking_section are "friends" with signals.c. The rest of the runtime is asked to use check_urgent_gc_and_callbacks or one of the public functions. Do you want me to document it?

@jhjourdan
Copy link
Copy Markdown
Contributor

No, that's fine. Leave it as-is.

@jhjourdan
Copy link
Copy Markdown
Contributor

Another failure of OCamlDebug on Windows.... I really don't know what this is, but this really looks like something we have to look at.

@jhjourdan
Copy link
Copy Markdown
Contributor

However, it really seems unrelated to this PR, since it triggers repeatedly on different PR since Monday. Thus, it should not prevent merging this PR.

I opened an issue for the CI failure : #9043.

@jhjourdan
Copy link
Copy Markdown
Contributor

@gasche : could we merge this?

@gasche
Copy link
Copy Markdown
Member

gasche commented Oct 16, 2019

It's really not easy for me, looking at the conversation, to tell that you have converged to a final state. (When I previously thought you had converged I was later told there was a bug, and I'm not completely sure which bug and how/whether it was solved.) I'm going to trust you on this.

@gasche gasche merged commit 8142044 into ocaml:trunk Oct 16, 2019
@gadmm
Copy link
Copy Markdown
Contributor Author

gadmm commented Oct 16, 2019

Thanks for merging. Indeed, we could have been clearer. I am very satisfied with the end result, in particular thanks to @jhjourdan's thorough review and explanations I think the good choices have been made.

@gadmm gadmm deleted the resource-safe-api-pt0 branch October 16, 2019 12:11
@jhjourdan
Copy link
Copy Markdown
Contributor

It's really not easy for me, looking at the conversation, to tell that you have converged to a final state. (When I previously thought you had converged I was later told there was a bug, and I'm not completely sure which bug and how/whether it was solved.) I'm going to trust you on this.

Sorry for that. I know this has not been clear, mostly my fault: I turned the green light on while I have not done my final, thorough review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants