[C API] Add refcount to wasm_engine_t singleton#1001
[C API] Add refcount to wasm_engine_t singleton#1001wenyongh merged 1 commit intobytecodealliance:mainfrom
Conversation
|
Additional note absent from commit message: unmodified wasmer's C API implementation already allows to safely create and destroy multiple engine instances. |
I am worried that unpaired |
|
Thanks for reviewing! I agree new/delete calls should be paired otherwise memory leaks can occur. This patch basically allows this: /* ABBA */
wasm_engine_t* a = wasm_engine_new();
wasm_engine_t* b = wasm_engine_new();
wasm_engine_delete(b);
// Continue working with engine A...
wasm_engine_delete(a);Right now any attempts to do the above result in unexpected behavior because: wasm_engine_delete(b); // makes both A and B invalidThe C API is enforcing the following implicit convention: /* AABB */
wasm_engine_t* a = wasm_engine_new();
wasm_engine_delete(a);
wasm_engine_t* b = wasm_engine_new();
wasm_engine_delete(b);In other words in the current implementation the engine new/delete calls are not balanced, hence it is only possible to have a single engine instance at a given time. (maybe this is a requirement of the WASM C API standard I am not aware of). The patch makes sure the runtime is not destroyed until the last call to |
|
I made some edits to the examples above to correct errors and make it more explicit |
|
Thanks. It does make sense. And it leaves us another problem. Although there is no accurate concept of an Maybe a global engine list ( |
|
There are two different issues here actually:
I thought of a somehow pragmatic approach for addressing #1 which is decoupling the library itself from the engine/runtime at a code level. Treat the former as a WAMR implementation detail, and engine as a standard entity which is already represented by That would require two WAMR custom functions like I must be honest here in the sense that I pursue the implementation with the least global data as possible since it is a requirement in my context. It sounds like a desirable concept anyways unless it makes the implementation over-complicated. As a bonus A global engine list also works, and I've already seen lists implemented internally for keeping track of created stores. For #2 I agree the reference cycle does not look very clean but having multiple engine instances instead of the current singleton means there is no other way for accessing the engine stores list from Less invasive option could be leaving things as they are now (singleton included) and add a reference counter to know when to call Please take all this with a grain of salt as my experience with WASM and WAMR is scarce. |
|
struct wasm_engine_t {
// ...
uint32_t reference_counts;
}; |
This is clear now, thanks.
Looks elegant and solves the problem! I think we have a similar issue with I recall having to disable |
|
Nice catch. The big assumption about one engine and one store has been broken. And we will see some crashing related to that. Maybe ref-counters can help them. But let me do some investigation first. Since it needs some tough work to fix all those, I am super curious about scenarios of multiple calling. Is it a multiple threads case? |
|
It is an audio plugin architecture where each plugin holds its own WASM engine. A plugin can be loaded multiple times into the same process (the DAW). The plugins are packaged into shared libraries with a standard interface (VST, LV2, etc). The goal is to run DSP modules precompiled from AssemblyScript. So if I correctly understand what is going on, loading two instances of a plugin results in calling This setup also reveals my previous concern with global/static data :) There is some threading involved. The host can call the plugin from multiple threads, including realtime. These native calls result in If there is some obvious downside in doing this or recommendations, I'd be happy to know about them! |
|
I'd like to remove the possibility of "thread-unsafe bh_vector", please try the crash with a patch and see where it goes. In wasm-c-api, we tend to follow the thread model.
|
Thanks for that link... wish I had seen it earlier :) . That statement is pretty clear and explains the singleton. Still #1001 (comment) sounds good to have.
Mm I think I accidentally made things worse for my specific setup. The patch contains new Also note the crashes weren't related to concurrency, access to WAMR was already synchronized using a spin lock. The crash happens when calling |
|
|
|
Well, it is an interesting call stack. I am afraid I am going to ask for more details.
|
|
Here is my test code, please take a look |
|
Thanks very much for the time on this and the test program. AOT is also crashing for my particular setup on Windows (MinGW build) but for different reasons, so it makes sense to have a thorough look before writing an answer. |
Sure, very eager to contribute on this. I managed to create a minimal project that simulates the host/plugin environment and recreates the exact issue experienced on a real DAW with my VST plugin, more on that below.
No error messages printed. I think this is expected because WAMR does not even have a chance to test for failure in this case. The crashing call is
Made this specific test and no, calling it twice from the same thread works without issues.
I tried threads.c and crashes maybe 1 out of 10 times. The crash happens in a different place compared to my project's, in Note: threads.c ends gracefully most of the times.
The To run: libvmlib.a compiled with Note: host.c ALWAYS crashes The issue on MinGW looks totally different, more on that later. Edit: the WAMR flags were incorrect, the attached static lib was compiled with |
|
Thanks a lot for exposing that wired problem. It is about a restriction about TLS and dynamic libraries loading on MacOS. I've updated some test code on gist. It includes CMakeLists.txt, main.c and plugin.c. I will keep you posted when I learne more or figure out how to fix that. |
|
Great. Some more evidence:
Generally speaking, I found workarounds for everything in my specific use case. The performance gains using AOT are huge. |
|
A simple answer is: Every plugin should link with a dynamic library libiwasm.so instead of a static library libvmlib.a. More details: The test case creates two shared libraries with plugin.c, they are lib_a_xx and lib_b_xx. After loading both plugin libraries, main.c will compare:
The test case will run twice with two different conditions:
Run the test case on Ubuntu and MacOS and get observations:
In you are still interested, here are some thoughts:
On Ubuntu, if we take a close look at the assemble code of lib_a or lib_b, we will see:
By tracing the code, I realize the dynamic linker will put a l_tls_modid in it. That leaves us the finally problem, why "those tls variables from different libraries (although from the same static library)" have the same |
|
Thanks for the detailed analysis. I don't see a lot deeper than the tip of the iceberg here and unfortunately there is not much more I can contribute other than testing and reporting back. This also confirms my initial thought that it is not a trivial issue to solve. Since mine is a somehow edge case, thinking practically, the first simple answer is already satisfying (do not link static). In fact I'm already doing this on Windows (my MinGW libiwasm.a build just does not work when AOT is enabled). Approach could be easily replicated on Linux and Mac. Fixing everything on every platform combination might be possible but I'm unsure about the need other than curiosity. I'm really repeating a mantra (prefer static for plugins), dynamic linking could come with its own side effects but I have to see those yet. That should be really studied on a case by case basis. Also it is a good opportunity to dig into OS internals. Not sure this can be "fixed" or just regarded as part of the operating conditions, at any case this is already great documentation for other projects with similar setups. Does it make sense that |
|
|
Thank you, yes that is right. I wasn't sure about other implications -- so far things work smoothly with that setting. |
|
Hi, there. May I ask if this patch is still necessary for your current solution? If yes, does it target the singleton |
|
Hi, my project does not depend on this very same patch but includes a different workaround . So far the best solution seems to be what you proposed above, which has the same effect as my workaround but allows to embed WAMR without special care:
Maybe I should close this PR and eventually start a new one? the title is now misleading too. |
|
I would prefer to update this PR with the new patch and change the title. |
Previously the following setup was not possible: wasm_engine_t *a = wasm_engine_new(); wasm_engine_t *b = wasm_engine_new(); wasm_engine_delete(b); wasm_engine_delete(a); Because wasm_engine_delete(b) would also deinitialize the full WAMR runtime. Keep track of references to the engine singleton to delay runtime deinit until the last reference to the engine is deleted.
|
Just force-pushed a commit that adds a reference counter to |
| singleton_engine = | ||
| wasm_engine_new_internal(Alloc_With_System_Allocator, NULL); | ||
| } | ||
| singleton_engine->ref_count++; |
There was a problem hiding this comment.
Hi, currently wasm-c-api APIs are not thread-safe, if to make them thread-safe, there might be more work to do. If you insist on it, could you please add a global lock, initialize it and lock/unlock it in the operations related to singleton:
after L314, define: static korp_mutex c_api_global_lock;
and here: use os_mutex_init(&c_api_global_lock) to initialize it, and check return value
We will merge #1010 to ensure the vector related operations be safe-thread, but if there is other issue occurring, we need to fix it.
This is now handled by WAMR itself bytecodealliance/wasm-micro-runtime#1001
This patch allows safer (note: safer, not safe) embedding in a plugin environment where multiple instances of the engine could be needed. Original code initializes and tears down the full runtime during wasm_engine_new() and wasm_engine_delete() respectively. After this update the C API implementation keeps track of engine instances count and inits/deinits the runtime only when needed. This allows for example to call wasm_engine_new() twice and then call wasm_engine_delete() once without rendering the first engine instance invalid.
This patch allows safer (note: safer, not safe) embedding in a plugin
environment where multiple instances of the engine could be needed.
Original code initializes and tears down the full runtime during
wasm_engine_new() and wasm_engine_delete() respectively. After this
update the C API implementation keeps track of engine instances count
and inits/deinits the runtime only when needed.
This allows for example to call wasm_engine_new() twice and then call
wasm_engine_delete() once without rendering the first engine instance
invalid.