Skip to content

Commit 6e09cc8

Browse files
committed
First PoC for solving threading issues in node loader.
1 parent 4768980 commit 6e09cc8

File tree

5 files changed

+436
-33
lines changed

5 files changed

+436
-33
lines changed

source/loaders/node_loader/source/node_loader_impl.cpp

Lines changed: 124 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ struct loader_impl_node_type
223223
loader_impl_async_initialize_safe initialize_safe;
224224
napi_threadsafe_function threadsafe_initialize;
225225

226+
/* TODO: Remove all napi_value and arguments from here -> */
226227
napi_value execution_path_safe_ptr;
227228
loader_impl_async_execution_path_safe execution_path_safe;
228229
napi_threadsafe_function threadsafe_execution_path;
@@ -243,8 +244,8 @@ struct loader_impl_node_type
243244
loader_impl_async_discover_safe discover_safe;
244245
napi_threadsafe_function threadsafe_discover;
245246

246-
napi_value func_call_safe_ptr;
247-
loader_impl_async_func_call_safe func_call_safe;
247+
// napi_value func_call_safe_ptr;
248+
// loader_impl_async_func_call_safe func_call_safe;
248249
napi_threadsafe_function threadsafe_func_call;
249250

250251
napi_value func_await_safe_ptr;
@@ -266,10 +267,12 @@ struct loader_impl_node_type
266267
napi_value destroy_safe_ptr;
267268
loader_impl_async_destroy_safe destroy_safe;
268269
napi_threadsafe_function threadsafe_destroy;
270+
/* TODO: -> To here*/
269271

270272
uv_thread_t thread;
271273
uv_loop_t *thread_loop;
272274

275+
/* TODO: Delete mutex and condition */
273276
uv_mutex_t mutex;
274277
uv_cond_t cond;
275278
std::atomic_bool locked;
@@ -372,6 +375,9 @@ struct loader_impl_async_func_call_safe_type
372375
size_t size;
373376
napi_value recv;
374377
function_return ret;
378+
379+
uv_mutex_t mutex;
380+
uv_cond_t cond;
375381
};
376382

377383
struct loader_impl_async_func_await_safe_type
@@ -1360,38 +1366,88 @@ int function_node_interface_create(function func, function_impl impl)
13601366
return (node_func->argv == NULL);
13611367
}
13621368

1369+
/* TODO: Convert this into a templated lambda */
1370+
void node_loader_impl_function_call_js_func_call_safe(napi_env env, napi_value js_callback, void *context, void *data)
1371+
{
1372+
loader_impl_async_func_call_safe func_call_safe = static_cast<loader_impl_async_func_call_safe>(data);
1373+
1374+
(void)js_callback;
1375+
(void)context;
1376+
1377+
if (env != NULL && js_callback != NULL)
1378+
{
1379+
/* Lock the call safe mutex and get the parameters */
1380+
uv_mutex_lock(&func_call_safe->mutex);
1381+
1382+
/* Store environment for reentrant calls */
1383+
func_call_safe->node_impl->env = env;
1384+
1385+
/* Call to the implementation function */
1386+
node_loader_impl_func_call_safe(env, func_call_safe);
1387+
1388+
/* Clear environment */
1389+
// func_call_cast.safe->node_impl->env = NULL;
1390+
1391+
/* Signal function call condition */
1392+
uv_cond_signal(&func_call_safe->cond);
1393+
1394+
uv_mutex_unlock(&func_call_safe->mutex);
1395+
}
1396+
}
1397+
13631398
function_return function_node_interface_invoke(function func, function_impl impl, function_args args, size_t size)
13641399
{
13651400
loader_impl_node_function node_func = (loader_impl_node_function)impl;
13661401

13671402
if (node_func != NULL)
13681403
{
13691404
loader_impl_node node_impl = node_func->node_impl;
1370-
function_return ret = NULL;
13711405
napi_status status;
13721406

1373-
/* Set up call safe arguments */
1374-
node_impl->func_call_safe->node_impl = node_impl;
1375-
node_impl->func_call_safe->func = func;
1376-
node_impl->func_call_safe->node_func = node_func;
1377-
node_impl->func_call_safe->args = static_cast<void **>(args);
1378-
node_impl->func_call_safe->size = size;
1379-
node_impl->func_call_safe->recv = NULL;
1380-
node_impl->func_call_safe->ret = NULL;
1381-
13821407
/* Check if we are in the JavaScript thread */
13831408
if (node_impl->js_thread_id == std::this_thread::get_id())
13841409
{
1410+
loader_impl_async_func_call_safe_type func_call_safe;
1411+
1412+
/* Set up call safe arguments */
1413+
func_call_safe.node_impl = node_impl;
1414+
func_call_safe.func = func;
1415+
func_call_safe.node_func = node_func;
1416+
func_call_safe.args = static_cast<void **>(args);
1417+
func_call_safe.size = size;
1418+
func_call_safe.recv = NULL;
1419+
func_call_safe.ret = NULL;
1420+
13851421
/* We are already in the V8 thread, we can call safely */
1386-
node_loader_impl_func_call_safe(node_impl->env, node_impl->func_call_safe);
1422+
node_loader_impl_func_call_safe(node_impl->env, &func_call_safe);
13871423

13881424
/* Set up return of the function call */
1389-
ret = node_impl->func_call_safe->ret;
1425+
return func_call_safe.ret;
13901426
}
1427+
1428+
/* TODO: Refactor this properly */
1429+
13911430
/* Lock the mutex and set the parameters */
1392-
else if (node_impl->locked.load() == false && uv_mutex_trylock(&node_impl->mutex) == 0)
1431+
// if (node_impl->locked.load() == false && uv_mutex_trylock(&node_impl->mutex) == 0)
13931432
{
1394-
node_impl->locked.store(true);
1433+
loader_impl_async_func_call_safe_type func_call_safe;
1434+
function_return ret = NULL;
1435+
1436+
// node_impl->locked.store(true);
1437+
1438+
/* Set up call safe arguments */
1439+
func_call_safe.node_impl = node_impl;
1440+
func_call_safe.func = func;
1441+
func_call_safe.node_func = node_func;
1442+
func_call_safe.args = static_cast<void **>(args);
1443+
func_call_safe.size = size;
1444+
func_call_safe.recv = NULL;
1445+
func_call_safe.ret = NULL;
1446+
1447+
uv_mutex_init(&func_call_safe.mutex);
1448+
uv_cond_init(&func_call_safe.cond);
1449+
1450+
uv_mutex_lock(&func_call_safe.mutex);
13951451

13961452
/* Acquire the thread safe function in order to do the call */
13971453
status = napi_acquire_threadsafe_function(node_impl->threadsafe_func_call);
@@ -1402,13 +1458,24 @@ function_return function_node_interface_invoke(function func, function_impl impl
14021458
}
14031459

14041460
/* Execute the thread safe call in a nonblocking manner */
1405-
status = napi_call_threadsafe_function(node_impl->threadsafe_func_call, nullptr, napi_tsfn_nonblocking);
1461+
status = napi_call_threadsafe_function(node_impl->threadsafe_func_call, &func_call_safe, napi_tsfn_nonblocking);
14061462

14071463
if (status != napi_ok)
14081464
{
14091465
log_write("metacall", LOG_LEVEL_ERROR, "Invalid to call to thread safe function invoke function in NodeJS loader");
14101466
}
14111467

1468+
/* Wait for the execution of the safe call */
1469+
uv_cond_wait(&func_call_safe.cond, &func_call_safe.mutex);
1470+
1471+
/* Set up return of the function call */
1472+
ret = func_call_safe.ret;
1473+
1474+
// node_impl->locked.store(false);
1475+
1476+
/* Unlock the mutex */
1477+
uv_mutex_unlock(&func_call_safe.mutex);
1478+
14121479
/* Release call safe function */
14131480
status = napi_release_threadsafe_function(node_impl->threadsafe_func_call, napi_tsfn_release);
14141481

@@ -1417,23 +1484,11 @@ function_return function_node_interface_invoke(function func, function_impl impl
14171484
log_write("metacall", LOG_LEVEL_ERROR, "Invalid to release thread safe function invoke function in NodeJS loader");
14181485
}
14191486

1420-
/* Wait for the execution of the safe call */
1421-
uv_cond_wait(&node_impl->cond, &node_impl->mutex);
1422-
1423-
/* Set up return of the function call */
1424-
ret = node_impl->func_call_safe->ret;
1425-
1426-
node_impl->locked.store(false);
1487+
uv_mutex_destroy(&func_call_safe.mutex);
1488+
uv_cond_destroy(&func_call_safe.cond);
14271489

1428-
/* Unlock the mutex */
1429-
uv_mutex_unlock(&node_impl->mutex);
1430-
}
1431-
else
1432-
{
1433-
log_write("metacall", LOG_LEVEL_ERROR, "Potential deadlock detected in function_node_interface_invoke, the call has not been executed in order to avoid the deadlock");
1490+
return ret;
14341491
}
1435-
1436-
return ret;
14371492
}
14381493

14391494
return NULL;
@@ -3876,15 +3931,51 @@ void *node_loader_impl_register(void *node_impl_ptr, void *env_ptr, void *functi
38763931

38773932
/* Safe function call */
38783933
{
3934+
/* TODO: Refactor this */
3935+
38793936
static const char threadsafe_func_name_str[] = "node_loader_impl_async_func_call_safe";
38803937

3938+
/*
38813939
node_loader_impl_thread_safe_function_initialize<loader_impl_async_func_call_safe_type>(
38823940
env,
38833941
threadsafe_func_name_str, sizeof(threadsafe_func_name_str),
38843942
&node_loader_impl_async_func_call_safe,
38853943
(loader_impl_async_func_call_safe_type **)(&node_impl->func_call_safe),
38863944
&node_impl->func_call_safe_ptr,
38873945
&node_impl->threadsafe_func_call);
3946+
*/
3947+
3948+
/*
3949+
void node_loader_impl_thread_safe_function_initialize(napi_env env,
3950+
const char name[], size_t size, napi_value (*callback)(napi_env, napi_callback_info), T **data,
3951+
napi_value *ptr, napi_threadsafe_function *threadsafe_function)
3952+
*/
3953+
3954+
napi_value func_call_safe_ptr;
3955+
3956+
/* Initialize call safe function with context */
3957+
status = napi_create_function(env, nullptr, 0, &node_loader_impl_async_func_call_safe, nullptr, &func_call_safe_ptr);
3958+
3959+
node_loader_impl_exception(env, status);
3960+
3961+
/* Create call safe function */
3962+
napi_value threadsafe_func_name;
3963+
3964+
status = napi_create_string_utf8(env, threadsafe_func_name_str, sizeof(threadsafe_func_name_str), &threadsafe_func_name);
3965+
3966+
node_loader_impl_exception(env, status);
3967+
3968+
// TODO: Does this number must be equivalent to the number of the threads of NodeJS?
3969+
unsigned int processor_count = std::thread::hardware_concurrency();
3970+
3971+
status = napi_create_threadsafe_function(env, func_call_safe_ptr,
3972+
nullptr, threadsafe_func_name,
3973+
0, processor_count,
3974+
nullptr, nullptr,
3975+
nullptr, &node_loader_impl_function_call_js_func_call_safe,
3976+
&node_impl->threadsafe_func_call);
3977+
3978+
node_loader_impl_exception(env, status);
38883979
}
38893980

38903981
/* Safe function await */
@@ -5389,7 +5480,7 @@ int node_loader_impl_destroy(loader_impl impl)
53895480
delete node_impl->load_from_memory_safe;
53905481
delete node_impl->clear_safe;
53915482
delete node_impl->discover_safe;
5392-
delete node_impl->func_call_safe;
5483+
// delete node_impl->func_call_safe;
53935484
delete node_impl->func_await_safe;
53945485
delete node_impl->func_destroy_safe;
53955486
delete node_impl->future_await_safe;

source/tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ add_subdirectory(metacall_node_python_deadlock_test)
159159
# add_subdirectory(metacall_node_signal_handler_test) # Note: Not used anymore but leaving it here for reference to solve this: https://github.com/metacall/core/issues/121
160160
add_subdirectory(metacall_node_native_code_test)
161161
add_subdirectory(metacall_node_extension_test)
162+
add_subdirectory(metacall_node_multithread_deadlock_test)
162163
add_subdirectory(metacall_distributable_test)
163164
add_subdirectory(metacall_cast_test)
164165
add_subdirectory(metacall_init_fini_test)

0 commit comments

Comments
 (0)