Skip to content

Commit 6b63ffe

Browse files
authored
Merge pull request #16 from benoitc/feature/executor-improvements
Improve executor pool configuration and fix idle GIL contention
2 parents 5653769 + 6d72b2e commit 6b63ffe

4 files changed

Lines changed: 28 additions & 16 deletions

File tree

c_src/py_exec.c

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -859,21 +859,20 @@ static void executor_stop(void) {
859859
/**
860860
* Main function for a multi-executor thread.
861861
* Each executor has its own queue and processes requests independently.
862+
*
863+
* GIL handling: Acquire GIL only when processing work, not while idle.
864+
* This prevents idle executors from competing with dirty schedulers
865+
* running actual Python work via the context-based API.
862866
*/
863867
static void *multi_executor_thread_main(void *arg) {
864868
executor_t *exec = (executor_t *)arg;
865869

866-
/* Acquire GIL for this thread */
867-
PyGILState_STATE gstate = PyGILState_Ensure();
868-
869870
exec->running = true;
870871

871872
while (!exec->shutdown) {
872873
py_request_t *req = NULL;
873874

874-
/* Release GIL while waiting for work */
875-
Py_BEGIN_ALLOW_THREADS
876-
875+
/* Wait for work - NO GIL held while idle */
877876
pthread_mutex_lock(&exec->mutex);
878877
while (exec->queue_head == NULL && !exec->shutdown) {
879878
pthread_cond_wait(&exec->cond, &exec->mutex);
@@ -890,8 +889,6 @@ static void *multi_executor_thread_main(void *arg) {
890889
}
891890
pthread_mutex_unlock(&exec->mutex);
892891

893-
Py_END_ALLOW_THREADS
894-
895892
if (req != NULL) {
896893
if (req->type == PY_REQ_SHUTDOWN) {
897894
pthread_mutex_lock(&req->mutex);
@@ -900,10 +897,16 @@ static void *multi_executor_thread_main(void *arg) {
900897
pthread_mutex_unlock(&req->mutex);
901898
break;
902899
} else {
903-
/* Process the request with GIL held */
900+
/* Acquire GIL only for actual work */
901+
PyGILState_STATE gstate = PyGILState_Ensure();
902+
903+
/* Process the request */
904904
process_request(req);
905905

906-
/* Signal completion */
906+
/* Release GIL immediately after processing */
907+
PyGILState_Release(gstate);
908+
909+
/* Signal completion (outside GIL) */
907910
pthread_mutex_lock(&req->mutex);
908911
req->completed = true;
909912
pthread_cond_signal(&req->cond);
@@ -913,7 +916,6 @@ static void *multi_executor_thread_main(void *arg) {
913916
}
914917

915918
exec->running = false;
916-
PyGILState_Release(gstate);
917919

918920
return NULL;
919921
}
@@ -953,8 +955,8 @@ static int multi_executor_start(int num_executors) {
953955
return 0;
954956
}
955957

956-
if (num_executors <= 0) {
957-
num_executors = 4;
958+
if (num_executors < MIN_EXECUTORS) {
959+
num_executors = MIN_EXECUTORS;
958960
}
959961
if (num_executors > MAX_EXECUTORS) {
960962
num_executors = MAX_EXECUTORS;

c_src/py_nif.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ static ERL_NIF_TERM nif_py_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM arg
710710
default:
711711
/* Start multiple executors for GIL contention mode */
712712
{
713-
int num_exec = 4; /* Default */
713+
int num_exec = MIN_EXECUTORS; /* Fallback if not provided */
714714
/* Check for config */
715715
if (argc > 0 && enif_is_map(env, argv[0])) {
716716
ERL_NIF_TERM key = enif_make_atom(env, "num_executors");

c_src/py_nif.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1063,11 +1063,17 @@ typedef struct {
10631063
* @{
10641064
*/
10651065

1066+
/**
1067+
* @def MIN_EXECUTORS
1068+
* @brief Minimum number of executor threads in the pool
1069+
*/
1070+
#define MIN_EXECUTORS 2
1071+
10661072
/**
10671073
* @def MAX_EXECUTORS
10681074
* @brief Maximum number of executor threads in the pool
10691075
*/
1070-
#define MAX_EXECUTORS 16
1076+
#define MAX_EXECUTORS 32
10711077

10721078
/**
10731079
* @struct executor_t

src/erlang_python_sup.erl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,12 @@ init([]) ->
3737
ContextMode = application:get_env(erlang_python, context_mode, auto),
3838
NumAsyncWorkers = application:get_env(erlang_python, num_async_workers, 2),
3939

40+
%% Default executors: 4 (benchmarked sweet spot for most workloads)
41+
%% Can be overridden via {erlang_python, [{num_executors, N}]}
42+
NumExecutors = application:get_env(erlang_python, num_executors, 4),
43+
4044
%% Initialize Python runtime first
41-
ok = py_nif:init(),
45+
ok = py_nif:init(#{num_executors => NumExecutors}),
4246

4347
%% Initialize the semaphore ETS table for rate limiting
4448
ok = py_semaphore:init(),

0 commit comments

Comments
 (0)