Skip to content

Commit dfcbe5d

Browse files
committed
Add configurable runner module to py_asgi:run/5
- py_asgi.erl: Extract runner option from Opts, default to hornbeam_asgi_runner - py_nif.erl: Change asgi_run/4 to asgi_run/5 with Runner as first param - py_asgi.c: Accept runner module as argv[0], use dynamic import instead of hardcoded module name - py_nif.c: Update NIF table arity from 4 to 5 This allows different ASGI runner implementations to be used with the optimized NIF marshalling path.
1 parent 6ecb4bd commit dfcbe5d

5 files changed

Lines changed: 29 additions & 22 deletions

File tree

c_src/py_asgi.c

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,7 +1123,7 @@ static ERL_NIF_TERM nif_asgi_build_scope(ErlNifEnv *env, int argc, const ERL_NIF
11231123
}
11241124

11251125
static ERL_NIF_TERM nif_asgi_run(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
1126-
if (argc < 4) {
1126+
if (argc < 5) {
11271127
return make_error(env, "badarg");
11281128
}
11291129

@@ -1135,23 +1135,28 @@ static ERL_NIF_TERM nif_asgi_run(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
11351135
return make_error(env, "asgi_not_initialized");
11361136
}
11371137

1138-
ErlNifBinary module_bin, callable_bin;
1138+
ErlNifBinary runner_bin, module_bin, callable_bin;
11391139

1140-
if (!enif_inspect_binary(env, argv[0], &module_bin)) {
1140+
if (!enif_inspect_binary(env, argv[0], &runner_bin)) {
1141+
return make_error(env, "invalid_runner");
1142+
}
1143+
if (!enif_inspect_binary(env, argv[1], &module_bin)) {
11411144
return make_error(env, "invalid_module");
11421145
}
1143-
if (!enif_inspect_binary(env, argv[1], &callable_bin)) {
1146+
if (!enif_inspect_binary(env, argv[2], &callable_bin)) {
11441147
return make_error(env, "invalid_callable");
11451148
}
11461149

11471150
PyGILState_STATE gstate = PyGILState_Ensure();
11481151

11491152
ERL_NIF_TERM result;
11501153

1151-
/* Convert module and callable names */
1154+
/* Convert runner, module and callable names */
1155+
char *runner_name = binary_to_string(&runner_bin);
11521156
char *module_name = binary_to_string(&module_bin);
11531157
char *callable_name = binary_to_string(&callable_bin);
1154-
if (module_name == NULL || callable_name == NULL) {
1158+
if (runner_name == NULL || module_name == NULL || callable_name == NULL) {
1159+
enif_free(runner_name);
11551160
enif_free(module_name);
11561161
enif_free(callable_name);
11571162
PyGILState_Release(gstate);
@@ -1174,24 +1179,24 @@ static ERL_NIF_TERM nif_asgi_run(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
11741179
}
11751180

11761181
/* Build optimized scope dict from Erlang map */
1177-
PyObject *scope = asgi_scope_from_map(env, argv[2]);
1182+
PyObject *scope = asgi_scope_from_map(env, argv[3]);
11781183
if (scope == NULL) {
11791184
Py_DECREF(asgi_app);
11801185
result = make_py_error(env);
11811186
goto cleanup;
11821187
}
11831188

11841189
/* Convert body binary */
1185-
PyObject *body = asgi_binary_to_buffer(env, argv[3]);
1190+
PyObject *body = asgi_binary_to_buffer(env, argv[4]);
11861191
if (body == NULL) {
11871192
Py_DECREF(scope);
11881193
Py_DECREF(asgi_app);
11891194
result = make_py_error(env);
11901195
goto cleanup;
11911196
}
11921197

1193-
/* Import the ASGI runner from hornbeam */
1194-
PyObject *runner_module = PyImport_ImportModule("hornbeam_asgi_runner");
1198+
/* Import the ASGI runner module */
1199+
PyObject *runner_module = PyImport_ImportModule(runner_name);
11951200
if (runner_module == NULL) {
11961201
/* Fallback: try to run ASGI app directly with asyncio.run */
11971202
PyErr_Clear();
@@ -1213,11 +1218,11 @@ static ERL_NIF_TERM nif_asgi_run(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
12131218
Py_DECREF(body);
12141219
Py_DECREF(scope);
12151220
Py_DECREF(asgi_app);
1216-
result = make_error(env, "hornbeam_asgi_runner_required");
1221+
result = make_error(env, "runner_module_required");
12171222
goto cleanup;
12181223
}
12191224

1220-
/* Call run_asgi(module_name, callable_name, scope, body) */
1225+
/* Call _run_asgi_sync(module_name, callable_name, scope, body) */
12211226
PyObject *run_result = PyObject_CallMethod(
12221227
runner_module, "_run_asgi_sync", "ssOO",
12231228
module_name, callable_name, scope, body);
@@ -1239,6 +1244,7 @@ static ERL_NIF_TERM nif_asgi_run(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
12391244
result = enif_make_tuple2(env, ATOM_OK, term_result);
12401245

12411246
cleanup:
1247+
enif_free(runner_name);
12421248
enif_free(module_name);
12431249
enif_free(callable_name);
12441250
PyGILState_Release(gstate);

c_src/py_asgi.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -470,12 +470,12 @@ static PyObject *asgi_scope_from_map(ErlNifEnv *env, ERL_NIF_TERM scope_map);
470470
* - Uses response pooling
471471
* - Runs ASGI app coroutine synchronously
472472
*
473-
* NIF signature: py_asgi:run(AppModule, AppCallable, ScopeMap, BodyBinary) ->
473+
* NIF signature: py_asgi:run(Runner, AppModule, AppCallable, ScopeMap, BodyBinary) ->
474474
* {ok, {Status, Headers, Body}} | {error, Reason}
475475
*
476476
* @param env NIF environment
477-
* @param argc Argument count (must be 4)
478-
* @param argv [AppModule, AppCallable, ScopeMap, BodyBinary]
477+
* @param argc Argument count (must be 5)
478+
* @param argv [Runner, AppModule, AppCallable, ScopeMap, BodyBinary]
479479
* @return Result tuple
480480
*/
481481
static ERL_NIF_TERM nif_asgi_run(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);

c_src/py_nif.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1870,7 +1870,7 @@ static ErlNifFunc nif_funcs[] = {
18701870

18711871
/* ASGI optimizations */
18721872
{"asgi_build_scope", 1, nif_asgi_build_scope, ERL_NIF_DIRTY_JOB_IO_BOUND},
1873-
{"asgi_run", 4, nif_asgi_run, ERL_NIF_DIRTY_JOB_IO_BOUND}
1873+
{"asgi_run", 5, nif_asgi_run, ERL_NIF_DIRTY_JOB_IO_BOUND}
18741874
};
18751875

18761876
ERL_NIF_INIT(py_nif, nif_funcs, load, NULL, upgrade, unload)

src/py_asgi.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,10 @@ run(Module, Callable, Scope, Body) ->
153153
%% @returns {ok, {Status, Headers, Body}} on success
154154
-spec run(binary(), binary(), scope(), binary(), map()) ->
155155
{ok, {integer(), [{binary(), binary()}], binary()}} | {error, term()}.
156-
run(Module, Callable, Scope, Body, _Opts) ->
157-
%% Ensure scope has required ASGI fields
156+
run(Module, Callable, Scope, Body, Opts) ->
157+
Runner = maps:get(runner, Opts, <<"hornbeam_asgi_runner">>),
158158
FullScope = ensure_scope_defaults(Scope),
159-
py_nif:asgi_run(Module, Callable, FullScope, Body).
159+
py_nif:asgi_run(Runner, Module, Callable, FullScope, Body).
160160

161161
%% @doc Build an optimized Python scope dict.
162162
%%

src/py_nif.erl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
set_python_event_loop/1,
115115
%% ASGI optimizations
116116
asgi_build_scope/1,
117-
asgi_run/4
117+
asgi_run/5
118118
]).
119119

120120
-on_load(load_nif/0).
@@ -762,13 +762,14 @@ asgi_build_scope(_ScopeMap) ->
762762
%% which provides the `_run_asgi_sync` function that handles the
763763
%% ASGI receive/send protocol.
764764
%%
765+
%% @param Runner Python runner module name (e.g., <<"hornbeam_asgi_runner">>)
765766
%% @param Module Application module name (e.g., <<"myapp.asgi">>)
766767
%% @param Callable ASGI callable name (e.g., <<"application">>)
767768
%% @param ScopeMap Erlang map containing ASGI scope (see asgi_build_scope/1)
768769
%% @param Body Request body as binary
769770
%% @returns {ok, {Status, Headers, Body}} on success,
770771
%% or {error, Reason}
771-
-spec asgi_run(binary(), binary(), map(), binary()) ->
772+
-spec asgi_run(binary(), binary(), binary(), map(), binary()) ->
772773
{ok, {integer(), [{binary(), binary()}], binary()}} | {error, term()}.
773-
asgi_run(_Module, _Callable, _ScopeMap, _Body) ->
774+
asgi_run(_Runner, _Module, _Callable, _ScopeMap, _Body) ->
774775
?NIF_STUB.

0 commit comments

Comments
 (0)