-
Notifications
You must be signed in to change notification settings - Fork 32
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
Comments on thread safety and Sync and Send? #133
Comments
I haven't experimented much with using ocaml-rs with multiple threads. Like you mentioned If this is something you're interested in looking into more I would be happy to accept a PR based on your findings! |
Are you currently doing any synchronization to serialize access to the OCaml runtime? FYI I've been getting segfaults when passing |
Not sure about OCaml 5, but with prior versions one was expected to register thread to OCaml runtime before executing any OCaml code on that thread. See this discuss topic. |
@emchristiansen what architecture did you end up with? I'm also playing around with attempts to integrate Lwt and Rust futures, and so far without much luck, I've created an executor that runs inside Lwt event loop, but so far it does not give me any advantages, Rust still condiders that I'm trying to move |
Updated: I actually ditched my in-house Executor implementation (that is 90% the one I got from async book anyways 😂 ) and used async_executor crate instead, and it worked! I passed asynchronous OCaml function to Rust, where that function moved into async task, that was scheduled by
I'm also running into segfaults but when I pass value to async that runs on a local executor (value is properly rooted so that OCaml does not GC it, see #134 (comment)), so even without any threads. I'm nearly sure that it's caused by OCaml GC moving the memory behind @zshipko maybe some additional Rust magic is required to tell Rust that |
Thanks for the report - this sounds like great progress! I will take a look at adding some lifetime constraints to custom types. |
After a quick look the lifetime approach will require some more investigation, but if the value is being moved then perhaps rooting the value until it's dropped on the Rust side might be another solution? |
How can I do that? I'm quite new to Rust. I've tried to So far I can confirm that after I moved my LocalExecutor into thread local static variable, I no longer see the segfault. No other changes to the setup that I currently have, so I'm convinced that the issue was due to relocation of the |
Ah, you mean rooting via OCaml API? I believe rooting does not stop the GC from moving it around as it sees fit. Root is just an integer, so as long as OCaml is able to find the pointer, it can actually move it. Does that sound right? |
I wonder if you tried using I can add some wrapper functions if that does work. |
Ah okay, I suppose I need to look into the guarantees that rooting provides a little deeper! |
Maybe |
Thanks for checking! Using pointers only sounds like it would work, I will try it out later today when I get a moment. Is your code available online somewhere? |
This SO answer also confirms that custom blocks are subject to move like any other OCaml heap objects.
It's a part of a larger codebase right now, and it's quite messy, I wanted to publish it somewhere, but it's nowhere near ready 😂 Just ping me here with some branch, I'll try it out, repro is stable and easy to get. Will that work for you? |
No problem, that works for me! |
@Lupus only store pointers instead of the full value, and also make sure to de-reference them from the Rust side when accessing them (don't keep references to the location, because that part of the heap is owned by OCaml) You probably want to pin them too. https://doc.rust-lang.org/std/pin/ Edit: dereferencing example |
Thanks @tizoc for the examples, I started to look at how to port that over to ocaml-rs but unfortunately wasn't able to with the time I had. @Lupus if you wanted to experiment with converting |
Thanks @tizoc for valuable pointers! @zshipko I'm not sure why it's necessary to port DynBox magic into ocaml-rs if it could just be used from So far I've tried to use /* #1 */
#[ocaml::func]
#[ocaml::sig("runtime -> unit")]
pub fn runtime_run_pending(rt: OCaml<DynBox<Runtime>>) {
while Borrow::<Runtime>::borrow(&rt).executor.try_tick() {}
}
/* #2 */
#[ocaml::func]
#[ocaml::sig("runtime -> unit")]
pub fn runtime_run_pending(rt: OCaml<DynBox<Runtime>>) {
let rt: &Runtime = rt.borrow();
while rt.executor.try_tick() {}
} First variant of this function is borrowing reference to runtime multiple times in the loop, executor is running it's tasks inside Second variant reads pointer in OCaml heap only once and consecutive relocation of that pointer does not wreak havoc any more. @tizoc is that what you mean by "make sure to de-reference them from the Rust side"? Can Rust type system be leveraged to stop me from dereferencing |
@Lupus it is very hard to follow what is going on from just code snippets and without being able to expand the macro code or what happens inside At first glance the first example should work, but I don't know if that value is rooted or not (if it is not, then whenever the GC runs it can become invalid). For a rooted value you would de-reference it every time, and then de-reference the contents too, ensuring that every time you get the correct pointer even if the OCaml runtime moved the memory around. |
I realized ocaml-rs also allows parameters of type |
Okay, I found some time to brush up the code that I had and actually published it on Github, meet ocaml-lwt-interop! I described how things work in README and even tried to illustrate the execution flow for a complex interop scenario with a sequence diagram. I ended up with some implementation of smart pointer on top of
@tizoc Sure, makes sense! I've reconstructed the repro with segfault on top of the version of the code that I published, you can find it [on a separate branch](https://github.com/Lupus/ocaml-lwt-interop/blob/ocaml-dynbox-segfault-repro/src/stubs.rs#L54]. Based on my understanding this segfault is somewhat expected, as |
Awesome, I will take a look!
I just pushed a commit to the |
I keep hitting the same kind of segfaul once again it seems. Easy to work-around, but I thought maybe there's some nice way to avoid it altogether... I've started a topic on the Rust forum to get wider discussion. The repro code is here, in a separate branch of ocaml-lwt-interop repo. Citing here the offending function: #[ocaml::func]
#[ocaml::sig("executor -> (int -> promise) -> unit")]
pub fn lwti_executor_test(executor: CamlRef<Executor>, _f: ocaml::Value) {
eprintln!("executor at #1: {:?}", executor);
let task = executor.spawn(async {
eprintln!("executor at #2: {:?}", executor);
executor.spawn(async {}).detach();
});
task.detach();
} Executor pointer inside the task somehow has different values inside the
Basically there is OCaml runtime doing arbitraty things after sequenceDiagram
participant lwt_loop as Lwt Event Loop (OCaml)
participant lwt_main as Lwt Main (OCaml)
participant rust_test as Executor test func (Rust)
participant runtime as Executor (Rust)
participant task as Task (Rust)
activate lwt_main
note right of lwt_main: creates Rust runtime
lwt_main ->> rust_test: Run test function
activate rust_test
rust_test ->> runtime: Spawns async task
activate runtime
runtime -->> lwt_loop: Trigger Lwt_unix notification
note right of runtime: Task is stored in runtime state
runtime ->> rust_test: Returns
deactivate runtime
rust_test ->> lwt_main: Returns
deactivate rust_test
note right of lwt_main: Sleeps...
deactivate lwt_main
note right of lwt_loop: Process Lwt_unix notification
lwt_loop ->> runtime: Run pending tasks
activate lwt_loop
activate runtime
runtime ->> task: Run
activate task
note left of task: Tries to access executor and crashes
deactivate task
I'm really lost on why
|
@zshipko I'm pretty sure that it's not GC collecting the executor value in my last example. It's referenced in OCaml side in Lwt unix notification callback, so it can't just go away. To double-check I've added explicit rooting of values in the repro case I described above and that did not change anything. |
Turns out that my segfaults were caused by me ignoring some safety constraints for spawning tasks, see this reply to my topic on rust forum. @zshipko have you had a chance to look at CamlRef? It seems to work just fine and is quite convenient to use in bindings. Maybe it's something worth including into |
Context: Calling OCaml from Rust
Now that wrapping
Lwt.t
is mostly usable, I'm trying to useocaml-rs
in async settings, and I'm running into the problem that none of theocaml-rs
types are labeledSync
orSend
.Is this deliberate?
If not, can they be safely labeled as such?
(I'm guessing nearly everything is just a pointer and thus safe to send between threads.)
Also, is calling to OCaml from Rust thread safe?
I'm guessing that if you wanted to enforce serial execution you could do that by just serializing access to the OCaml runtime object in Rust, but I didn't see that in the code (didn't look very hard).
The text was updated successfully, but these errors were encountered: