|
87 | 87 | * Suspended state management |
88 | 88 | * ============================================================================ */ |
89 | 89 |
|
90 | | -/** |
91 | | - * Check if a SuspensionRequired exception is pending. |
92 | | - * Returns true if the exception is set and matches SuspensionRequiredException. |
93 | | - */ |
94 | | -static bool is_suspension_exception(void) { |
95 | | - if (!PyErr_Occurred()) { |
96 | | - return false; |
97 | | - } |
98 | | - return PyErr_ExceptionMatches(SuspensionRequiredException); |
99 | | -} |
100 | | - |
101 | | -/** |
102 | | - * Extract suspension info from the pending SuspensionRequired exception. |
103 | | - * Returns the exception args tuple (callback_id, func_name, args) or NULL. |
104 | | - * Clears the exception if successful. |
105 | | - */ |
106 | | -static PyObject *get_suspension_args(void) { |
107 | | - PyObject *exc_type, *exc_value, *exc_tb; |
108 | | - PyErr_Fetch(&exc_type, &exc_value, &exc_tb); |
109 | | - |
110 | | - if (exc_value == NULL) { |
111 | | - Py_XDECREF(exc_type); |
112 | | - Py_XDECREF(exc_tb); |
113 | | - return NULL; |
114 | | - } |
115 | | - |
116 | | - /* Get the args from the exception - it's a tuple (callback_id, func_name, args) */ |
117 | | - PyObject *args = PyObject_GetAttrString(exc_value, "args"); |
118 | | - Py_DECREF(exc_type); |
119 | | - Py_DECREF(exc_value); |
120 | | - Py_XDECREF(exc_tb); |
121 | | - |
122 | | - if (args == NULL || !PyTuple_Check(args) || PyTuple_Size(args) != 3) { |
123 | | - Py_XDECREF(args); |
124 | | - return NULL; |
125 | | - } |
126 | | - |
127 | | - return args; /* Caller owns this reference */ |
128 | | -} |
129 | | - |
130 | 90 | /** |
131 | 91 | * Create a suspended state resource from exception args. |
132 | 92 | * Args tuple format: (callback_id, func_name, args) |
@@ -1193,23 +1153,46 @@ static ERL_NIF_TERM nif_resume_callback_dirty(ErlNifEnv *env, int argc, const ER |
1193 | 1153 | Py_XDECREF(kwargs); |
1194 | 1154 |
|
1195 | 1155 | if (py_result == NULL) { |
1196 | | - if (is_suspension_exception()) { |
| 1156 | + if (tl_pending_callback) { |
1197 | 1157 | /* |
1198 | | - * Another suspension during replay - Python made a second erlang.call(). |
1199 | | - * Create a new suspended state and return {suspended, ...} so Erlang |
1200 | | - * can handle this callback and resume again. |
| 1158 | + * Flag-based callback detection during replay. |
| 1159 | + * Check flag FIRST, not exception type - this works even if |
| 1160 | + * Python code caught and re-raised the exception. |
1201 | 1161 | */ |
1202 | | - PyObject *exc_args = get_suspension_args(); /* Clears exception */ |
| 1162 | + PyErr_Clear(); /* Clear whatever exception is set */ |
| 1163 | + |
| 1164 | + /* Build exc_args tuple from thread-local storage */ |
| 1165 | + PyObject *exc_args = PyTuple_New(3); |
1203 | 1166 | if (exc_args == NULL) { |
1204 | | - result = make_error(env, "get_suspension_args_failed"); |
| 1167 | + tl_pending_callback = false; |
| 1168 | + result = make_error(env, "alloc_exc_args_failed"); |
1205 | 1169 | } else { |
1206 | | - suspended_state_t *new_suspended = create_suspended_state_from_existing(env, exc_args, state); |
1207 | | - if (new_suspended == NULL) { |
| 1170 | + PyObject *callback_id_obj = PyLong_FromUnsignedLongLong(tl_pending_callback_id); |
| 1171 | + PyObject *func_name_obj = PyUnicode_FromStringAndSize( |
| 1172 | + tl_pending_func_name, tl_pending_func_name_len); |
| 1173 | + |
| 1174 | + if (callback_id_obj == NULL || func_name_obj == NULL) { |
| 1175 | + Py_XDECREF(callback_id_obj); |
| 1176 | + Py_XDECREF(func_name_obj); |
1208 | 1177 | Py_DECREF(exc_args); |
1209 | | - result = make_error(env, "create_nested_suspended_state_failed"); |
| 1178 | + tl_pending_callback = false; |
| 1179 | + result = make_error(env, "build_exc_args_failed"); |
1210 | 1180 | } else { |
1211 | | - result = make_suspended_term(env, new_suspended, exc_args); |
1212 | | - Py_DECREF(exc_args); |
| 1181 | + PyTuple_SET_ITEM(exc_args, 0, callback_id_obj); |
| 1182 | + PyTuple_SET_ITEM(exc_args, 1, func_name_obj); |
| 1183 | + Py_INCREF(tl_pending_args); |
| 1184 | + PyTuple_SET_ITEM(exc_args, 2, tl_pending_args); |
| 1185 | + |
| 1186 | + suspended_state_t *new_suspended = create_suspended_state_from_existing(env, exc_args, state); |
| 1187 | + if (new_suspended == NULL) { |
| 1188 | + Py_DECREF(exc_args); |
| 1189 | + tl_pending_callback = false; |
| 1190 | + result = make_error(env, "create_nested_suspended_state_failed"); |
| 1191 | + } else { |
| 1192 | + result = make_suspended_term(env, new_suspended, exc_args); |
| 1193 | + Py_DECREF(exc_args); |
| 1194 | + tl_pending_callback = false; |
| 1195 | + } |
1213 | 1196 | } |
1214 | 1197 | } |
1215 | 1198 | } else { |
@@ -1258,23 +1241,46 @@ static ERL_NIF_TERM nif_resume_callback_dirty(ErlNifEnv *env, int argc, const ER |
1258 | 1241 | Py_DECREF(compiled); |
1259 | 1242 |
|
1260 | 1243 | if (py_result == NULL) { |
1261 | | - if (is_suspension_exception()) { |
| 1244 | + if (tl_pending_callback) { |
1262 | 1245 | /* |
1263 | | - * Another suspension during replay - Python made a second erlang.call(). |
1264 | | - * Create a new suspended state and return {suspended, ...} so Erlang |
1265 | | - * can handle this callback and resume again. |
| 1246 | + * Flag-based callback detection during eval replay. |
| 1247 | + * Check flag FIRST, not exception type - this works even if |
| 1248 | + * Python code caught and re-raised the exception. |
1266 | 1249 | */ |
1267 | | - PyObject *exc_args = get_suspension_args(); /* Clears exception */ |
| 1250 | + PyErr_Clear(); /* Clear whatever exception is set */ |
| 1251 | + |
| 1252 | + /* Build exc_args tuple from thread-local storage */ |
| 1253 | + PyObject *exc_args = PyTuple_New(3); |
1268 | 1254 | if (exc_args == NULL) { |
1269 | | - result = make_error(env, "get_suspension_args_failed"); |
| 1255 | + tl_pending_callback = false; |
| 1256 | + result = make_error(env, "alloc_exc_args_failed"); |
1270 | 1257 | } else { |
1271 | | - suspended_state_t *new_suspended = create_suspended_state_from_existing(env, exc_args, state); |
1272 | | - if (new_suspended == NULL) { |
| 1258 | + PyObject *callback_id_obj = PyLong_FromUnsignedLongLong(tl_pending_callback_id); |
| 1259 | + PyObject *func_name_obj = PyUnicode_FromStringAndSize( |
| 1260 | + tl_pending_func_name, tl_pending_func_name_len); |
| 1261 | + |
| 1262 | + if (callback_id_obj == NULL || func_name_obj == NULL) { |
| 1263 | + Py_XDECREF(callback_id_obj); |
| 1264 | + Py_XDECREF(func_name_obj); |
1273 | 1265 | Py_DECREF(exc_args); |
1274 | | - result = make_error(env, "create_nested_suspended_state_failed"); |
| 1266 | + tl_pending_callback = false; |
| 1267 | + result = make_error(env, "build_exc_args_failed"); |
1275 | 1268 | } else { |
1276 | | - result = make_suspended_term(env, new_suspended, exc_args); |
1277 | | - Py_DECREF(exc_args); |
| 1269 | + PyTuple_SET_ITEM(exc_args, 0, callback_id_obj); |
| 1270 | + PyTuple_SET_ITEM(exc_args, 1, func_name_obj); |
| 1271 | + Py_INCREF(tl_pending_args); |
| 1272 | + PyTuple_SET_ITEM(exc_args, 2, tl_pending_args); |
| 1273 | + |
| 1274 | + suspended_state_t *new_suspended = create_suspended_state_from_existing(env, exc_args, state); |
| 1275 | + if (new_suspended == NULL) { |
| 1276 | + Py_DECREF(exc_args); |
| 1277 | + tl_pending_callback = false; |
| 1278 | + result = make_error(env, "create_nested_suspended_state_failed"); |
| 1279 | + } else { |
| 1280 | + result = make_suspended_term(env, new_suspended, exc_args); |
| 1281 | + Py_DECREF(exc_args); |
| 1282 | + tl_pending_callback = false; |
| 1283 | + } |
1278 | 1284 | } |
1279 | 1285 | } |
1280 | 1286 | } else { |
|
0 commit comments