@@ -290,50 +290,65 @@ static void process_request(py_request_t *req) {
290290 if (check_timeout_error ()) {
291291 PyErr_Clear ();
292292 req -> result = enif_make_tuple2 (env , ATOM_ERROR , ATOM_TIMEOUT );
293- } else if (is_suspension_exception () ) {
293+ } else if (tl_pending_callback ) {
294294 /*
295- * Python code called erlang.call() which raised SuspensionRequired.
296- * Create a suspended state and return {suspended, CallbackId, StateRef, {Func, Args}}
297- * so Erlang can execute the callback and then resume.
295+ * Flag-based callback detection: check flag FIRST, not exception type.
296+ * This works even if Python code caught and re-raised the exception.
298297 */
299- PyObject * exc_args = get_suspension_args (); /* Clears exception */
298+ PyErr_Clear (); /* Clear whatever exception is set */
299+
300+ /* Build exc_args tuple from thread-local storage */
301+ PyObject * exc_args = PyTuple_New (3 );
300302 if (exc_args == NULL ) {
301- req -> result = make_error (env , "get_suspension_args_failed" );
303+ tl_pending_callback = false;
304+ req -> result = make_error (env , "alloc_exc_args_failed" );
302305 } else {
303- suspended_state_t * suspended = create_suspended_state (env , exc_args , req );
304- if (suspended == NULL ) {
306+ PyObject * callback_id_obj = PyLong_FromUnsignedLongLong (tl_pending_callback_id );
307+ PyObject * func_name_obj = PyUnicode_FromStringAndSize (
308+ tl_pending_func_name , tl_pending_func_name_len );
309+
310+ if (callback_id_obj == NULL || func_name_obj == NULL ) {
311+ Py_XDECREF (callback_id_obj );
312+ Py_XDECREF (func_name_obj );
305313 Py_DECREF (exc_args );
306- req -> result = make_error (env , "create_suspended_state_failed" );
314+ tl_pending_callback = false;
315+ req -> result = make_error (env , "build_exc_args_failed" );
307316 } else {
308- /* Extract callback info from exception args */
309- PyObject * callback_id_obj = PyTuple_GetItem (exc_args , 0 );
310- PyObject * func_name_obj = PyTuple_GetItem ( exc_args , 1 );
311- PyObject * call_args_obj = PyTuple_GetItem (exc_args , 2 );
317+ PyTuple_SET_ITEM ( exc_args , 0 , callback_id_obj );
318+ PyTuple_SET_ITEM (exc_args , 1 , func_name_obj );
319+ Py_INCREF ( tl_pending_args ); /* Tuple takes ownership */
320+ PyTuple_SET_ITEM (exc_args , 2 , tl_pending_args );
312321
313- uint64_t callback_id = PyLong_AsUnsignedLongLong (callback_id_obj );
314- Py_ssize_t fn_len ;
315- const char * fn = PyUnicode_AsUTF8AndSize (func_name_obj , & fn_len );
322+ suspended_state_t * suspended = create_suspended_state (env , exc_args , req );
323+ if (suspended == NULL ) {
324+ Py_DECREF (exc_args );
325+ tl_pending_callback = false;
326+ req -> result = make_error (env , "create_suspended_state_failed" );
327+ } else {
328+ /* Create Erlang terms */
329+ ERL_NIF_TERM state_ref = enif_make_resource (env , suspended );
330+ enif_release_resource (suspended );
316331
317- /* Create Erlang terms */
318- ERL_NIF_TERM state_ref = enif_make_resource (env , suspended );
319- enif_release_resource (suspended ); /* Erlang now holds the reference */
332+ ERL_NIF_TERM callback_id_term = enif_make_uint64 (env , tl_pending_callback_id );
320333
321- ERL_NIF_TERM callback_id_term = enif_make_uint64 (env , callback_id );
334+ ERL_NIF_TERM func_name_term ;
335+ unsigned char * fn_buf = enif_make_new_binary (env , tl_pending_func_name_len , & func_name_term );
336+ memcpy (fn_buf , tl_pending_func_name , tl_pending_func_name_len );
322337
323- ERL_NIF_TERM func_name_term ;
324- unsigned char * fn_buf = enif_make_new_binary (env , fn_len , & func_name_term );
325- memcpy (fn_buf , fn , fn_len );
338+ ERL_NIF_TERM args_term = py_to_term (env , tl_pending_args );
326339
327- ERL_NIF_TERM args_term = py_to_term ( env , call_args_obj );
340+ Py_DECREF ( exc_args );
328341
329- Py_DECREF (exc_args );
342+ /* Clear pending state */
343+ tl_pending_callback = false;
330344
331- /* Return {suspended, CallbackId, StateRef, {FuncName, Args}} */
332- req -> result = enif_make_tuple4 (env ,
333- ATOM_SUSPENDED ,
334- callback_id_term ,
335- state_ref ,
336- enif_make_tuple2 (env , func_name_term , args_term ));
345+ /* Return {suspended, CallbackId, StateRef, {FuncName, Args}} */
346+ req -> result = enif_make_tuple4 (env ,
347+ ATOM_SUSPENDED ,
348+ callback_id_term ,
349+ state_ref ,
350+ enif_make_tuple2 (env , func_name_term , args_term ));
351+ }
337352 }
338353 }
339354 } else {
@@ -404,43 +419,57 @@ static void process_request(py_request_t *req) {
404419 if (check_timeout_error ()) {
405420 PyErr_Clear ();
406421 req -> result = enif_make_tuple2 (env , ATOM_ERROR , ATOM_TIMEOUT );
407- } else if (is_suspension_exception ()) {
408- /* Handle suspension from erlang.call() in eval */
409- PyObject * exc_args = get_suspension_args (); /* Clears exception */
422+ } else if (tl_pending_callback ) {
423+ /* Flag-based callback detection for eval */
424+ PyErr_Clear ();
425+
426+ PyObject * exc_args = PyTuple_New (3 );
410427 if (exc_args == NULL ) {
411- req -> result = make_error (env , "get_suspension_args_failed" );
428+ tl_pending_callback = false;
429+ req -> result = make_error (env , "alloc_exc_args_failed" );
412430 } else {
413- suspended_state_t * suspended = create_suspended_state (env , exc_args , req );
414- if (suspended == NULL ) {
415- Py_DECREF (exc_args );
416- req -> result = make_error (env , "create_suspended_state_failed" );
417- } else {
418- PyObject * callback_id_obj = PyTuple_GetItem (exc_args , 0 );
419- PyObject * func_name_obj = PyTuple_GetItem (exc_args , 1 );
420- PyObject * call_args_obj = PyTuple_GetItem (exc_args , 2 );
421-
422- uint64_t callback_id = PyLong_AsUnsignedLongLong (callback_id_obj );
423- Py_ssize_t fn_len ;
424- const char * fn = PyUnicode_AsUTF8AndSize (func_name_obj , & fn_len );
425-
426- ERL_NIF_TERM state_ref = enif_make_resource (env , suspended );
427- enif_release_resource (suspended );
428-
429- ERL_NIF_TERM callback_id_term = enif_make_uint64 (env , callback_id );
430-
431- ERL_NIF_TERM func_name_term ;
432- unsigned char * fn_buf = enif_make_new_binary (env , fn_len , & func_name_term );
433- memcpy (fn_buf , fn , fn_len );
434-
435- ERL_NIF_TERM args_term = py_to_term (env , call_args_obj );
431+ PyObject * callback_id_obj = PyLong_FromUnsignedLongLong (tl_pending_callback_id );
432+ PyObject * func_name_obj = PyUnicode_FromStringAndSize (
433+ tl_pending_func_name , tl_pending_func_name_len );
436434
435+ if (callback_id_obj == NULL || func_name_obj == NULL ) {
436+ Py_XDECREF (callback_id_obj );
437+ Py_XDECREF (func_name_obj );
437438 Py_DECREF (exc_args );
438-
439- req -> result = enif_make_tuple4 (env ,
440- ATOM_SUSPENDED ,
441- callback_id_term ,
442- state_ref ,
443- enif_make_tuple2 (env , func_name_term , args_term ));
439+ tl_pending_callback = false;
440+ req -> result = make_error (env , "build_exc_args_failed" );
441+ } else {
442+ PyTuple_SET_ITEM (exc_args , 0 , callback_id_obj );
443+ PyTuple_SET_ITEM (exc_args , 1 , func_name_obj );
444+ Py_INCREF (tl_pending_args );
445+ PyTuple_SET_ITEM (exc_args , 2 , tl_pending_args );
446+
447+ suspended_state_t * suspended = create_suspended_state (env , exc_args , req );
448+ if (suspended == NULL ) {
449+ Py_DECREF (exc_args );
450+ tl_pending_callback = false;
451+ req -> result = make_error (env , "create_suspended_state_failed" );
452+ } else {
453+ ERL_NIF_TERM state_ref = enif_make_resource (env , suspended );
454+ enif_release_resource (suspended );
455+
456+ ERL_NIF_TERM callback_id_term = enif_make_uint64 (env , tl_pending_callback_id );
457+
458+ ERL_NIF_TERM func_name_term ;
459+ unsigned char * fn_buf = enif_make_new_binary (env , tl_pending_func_name_len , & func_name_term );
460+ memcpy (fn_buf , tl_pending_func_name , tl_pending_func_name_len );
461+
462+ ERL_NIF_TERM args_term = py_to_term (env , tl_pending_args );
463+
464+ Py_DECREF (exc_args );
465+ tl_pending_callback = false;
466+
467+ req -> result = enif_make_tuple4 (env ,
468+ ATOM_SUSPENDED ,
469+ callback_id_term ,
470+ state_ref ,
471+ enif_make_tuple2 (env , func_name_term , args_term ));
472+ }
444473 }
445474 }
446475 } else {
0 commit comments