@@ -1077,39 +1077,114 @@ PyTasklet_Throw_M(PyTaskletObject *self, int pending, PyObject *exc,
1077
1077
}
1078
1078
1079
1079
static PyObject *
1080
- impl_tasklet_throw (PyTaskletObject * self , int pending , PyObject * exc , PyObject * val , PyObject * tb )
1080
+ #if PY_VERSION_HEX < 0x03030700
1081
+ #define SLP_IMPL_THROW_BOMB_WITH_BOE 1
1082
+ _impl_tasklet_throw_bomb (PyTaskletObject * self , int pending , PyObject * bomb , int bomb_on_error )
1083
+ #else
1084
+ #undef SLP_IMPL_THROW_BOMB_WITH_BOE
1085
+ _impl_tasklet_throw_bomb (PyTaskletObject * self , int pending , PyObject * bomb )
1086
+ #endif
1081
1087
{
1082
1088
STACKLESS_GETARG ();
1083
1089
PyThreadState * ts = PyThreadState_GET ();
1084
- PyObject * ret , * bomb , * tmpval ;
1090
+ PyObject * ret , * tmpval ;
1085
1091
int fail ;
1086
1092
1087
- if (ts -> st .main == NULL )
1088
- return PyTasklet_Throw_M (self , pending , exc , val , tb );
1089
-
1090
- bomb = slp_exc_to_bomb (exc , val , tb );
1091
- if (bomb == NULL )
1092
- return NULL ;
1093
-
1093
+ assert (bomb != NULL );
1094
+ assert (PyBomb_Check (bomb ));
1094
1095
/* raise it directly if target is ourselves. delayed exception makes
1095
1096
* no sense in this case
1096
1097
*/
1097
- if (ts -> st .current == self )
1098
+ if (ts -> st .current == self ) {
1099
+ assert (self -> cstate -> tstate == ts );
1098
1100
return slp_bomb_explode (bomb );
1101
+ }
1099
1102
1100
- /* don't attempt to send to a dead tasklet.
1101
- * f.frame is null for the running tasklet and a dead tasklet
1102
- * A new tasklet has a CFrame
1103
+ /* Handle new or dead tasklets.
1103
1104
*/
1104
- if (self -> cstate -> tstate == NULL || (self -> f .frame == NULL && self != self -> cstate -> tstate -> st .current )) {
1105
- /* however, allow tasklet exit errors for already dead tasklets */
1106
- if (PyObject_IsSubclass (((PyBombObject * )bomb )-> curexc_type , PyExc_TaskletExit )) {
1105
+ if (slp_get_frame (self ) == NULL ) {
1106
+ /* The tasklet is not alive.
1107
+ * There are a few special cases:
1108
+ * - The purpose of raising exception TaskletExit is to end the tasklet. Therefore
1109
+ * it is no error, if the tasklet already run to its end.
1110
+ * - Otherwise we have to raise a RuntimeError.
1111
+ */
1112
+ if (!PyObject_IsSubclass (((PyBombObject * )bomb )-> curexc_type , PyExc_TaskletExit ) ||
1113
+ (self -> cstate -> tstate == NULL && self -> f .frame != NULL )) {
1114
+ /* Error: the exception is not TaskletExit or the tasklet did not run to its end. */
1115
+ #ifdef SLP_IMPL_THROW_BOMB_WITH_BOE
1116
+ if (bomb_on_error )
1117
+ return slp_bomb_explode (bomb );
1118
+ #endif
1107
1119
Py_DECREF (bomb );
1108
- Py_RETURN_NONE ;
1120
+ if (self -> cstate -> tstate == NULL ) {
1121
+ RUNTIME_ERROR ("tasklet has no thread" , NULL );
1122
+ }
1123
+ RUNTIME_ERROR ("You cannot throw to a dead tasklet" , NULL );
1124
+ }
1125
+ /* A TaskletExit exception. The tasklet already ended (== can't be
1126
+ * resurrected by bind_thread).
1127
+ * Simply end the tasklet.
1128
+ */
1129
+ /* next two if()... just for test coverage mesurement */
1130
+ if (self -> cstate -> tstate != NULL ) {
1131
+ assert (self -> cstate -> tstate != NULL );
1132
+ }
1133
+ if (self -> f .frame == NULL ) {
1134
+ assert (self -> f .frame == NULL );
1135
+ }
1136
+ Py_DECREF (bomb );
1137
+
1138
+ /* Now obey the post conditions of tasklet.throw:
1139
+ * 1. the tasklet is not blocked
1140
+ */
1141
+ if (self -> next && self -> flags .blocked ) {
1142
+ /* we claim the channel's reference */
1143
+ slp_channel_remove_slow (self , NULL , NULL , NULL );
1144
+ } else {
1145
+ Py_INCREF (self );
1109
1146
}
1147
+ /* Obey the post conditions of throw():
1148
+ * 2. the tasklet is not scheduled. This is also a precondition,
1149
+ * because a dead tasklet must not be scheduled.
1150
+ */
1151
+
1152
+ #if 0 /* disabled until https://bitbucket.org/stackless-dev/stackless/issues/81 is resolved */
1153
+ assert (self -> next == NULL && self -> prev == NULL );
1154
+ #endif
1155
+
1156
+ /* Due to bugs the above assertion my not hold.
1157
+ * Try to work around.
1158
+ */
1159
+ if (self -> next ) {
1160
+ if (self -> cstate -> tstate != NULL ) {
1161
+ /* The tasklet has a tstate an is scheduled.
1162
+ * we can use the regular remove.
1163
+ */
1164
+ slp_current_remove_tasklet (self );
1165
+ } else {
1166
+ /* The tasklet has no tstate, is not blocked on a channel.
1167
+ * This happens, if a thread ended, but the tasklet was
1168
+ * survived killing.
1169
+ */
1170
+ SLP_HEADCHAIN_REMOVE (self , prev , next );
1171
+ }
1172
+ Py_DECREF (self );
1173
+ }
1174
+ Py_DECREF (self ); /* the ref from the channel */
1175
+ Py_RETURN_NONE ;
1176
+ }
1177
+ assert (self -> cstate -> tstate != NULL );
1178
+ /* don't modify a tasklet on an uninitialised or dead thread */
1179
+ if (pending && self -> cstate -> tstate -> st .main == NULL ) {
1180
+ #ifdef SLP_IMPL_THROW_BOMB_WITH_BOE
1181
+ if (bomb_on_error )
1182
+ return slp_bomb_explode (bomb );
1183
+ #endif
1110
1184
Py_DECREF (bomb );
1111
- RUNTIME_ERROR ("You cannot throw to a dead tasklet " , NULL );
1185
+ RUNTIME_ERROR ("Target thread isn't initialised " , NULL );
1112
1186
}
1187
+
1113
1188
TASKLET_CLAIMVAL (self , & tmpval );
1114
1189
TASKLET_SETVAL_OWN (self , bomb );
1115
1190
if (!pending ) {
@@ -1141,6 +1216,30 @@ impl_tasklet_throw(PyTaskletObject *self, int pending, PyObject *exc, PyObject *
1141
1216
return ret ;
1142
1217
}
1143
1218
1219
+ static PyObject *
1220
+ impl_tasklet_throw (PyTaskletObject * self , int pending , PyObject * exc , PyObject * val , PyObject * tb )
1221
+ {
1222
+ STACKLESS_GETARG ();
1223
+ PyThreadState * ts = PyThreadState_GET ();
1224
+ PyObject * ret , * bomb ;
1225
+
1226
+ if (ts -> st .main == NULL )
1227
+ return PyTasklet_Throw_M (self , pending , exc , val , tb );
1228
+
1229
+ bomb = slp_exc_to_bomb (exc , val , tb );
1230
+ if (bomb == NULL )
1231
+ return NULL ;
1232
+
1233
+ STACKLESS_PROMOTE_ALL ();
1234
+ #ifdef SLP_IMPL_THROW_BOMB_WITH_BOE
1235
+ ret = _impl_tasklet_throw_bomb (self , pending , bomb , 0 );
1236
+ #else
1237
+ ret = _impl_tasklet_throw_bomb (self , pending , bomb );
1238
+ #endif
1239
+ STACKLESS_ASSERT ();
1240
+ return ret ;
1241
+ }
1242
+
1144
1243
int PyTasklet_Throw (PyTaskletObject * self , int pending , PyObject * exc ,
1145
1244
PyObject * val , PyObject * tb )
1146
1245
{
@@ -1191,27 +1290,22 @@ impl_tasklet_raise_exception(PyTaskletObject *self, PyObject *klass, PyObject *a
1191
1290
{
1192
1291
STACKLESS_GETARG ();
1193
1292
PyThreadState * ts = PyThreadState_GET ();
1194
- PyObject * ret , * bomb , * tmpval ;
1195
- int fail ;
1293
+ PyObject * ret , * bomb ;
1196
1294
1197
1295
if (ts -> st .main == NULL )
1198
1296
return PyTasklet_RaiseException_M (self , klass , args );
1199
1297
bomb = slp_make_bomb (klass , args , "tasklet.raise_exception" );
1200
1298
if (bomb == NULL )
1201
1299
return NULL ;
1202
- if (ts -> st .current == self )
1203
- return slp_bomb_explode (bomb );
1204
- /* if the tasklet is dead, do not run it (no frame) but explode */
1205
- if (slp_get_frame (self ) == NULL )
1206
- return slp_bomb_explode (bomb );
1207
1300
1208
- TASKLET_CLAIMVAL (self , & tmpval );
1209
- TASKLET_SETVAL_OWN (self , bomb );
1210
- fail = slp_schedule_task (& ret , ts -> st .current , self , stackless , 0 );
1211
- if (fail )
1212
- TASKLET_SETVAL_OWN (self , tmpval );
1213
- else
1214
- Py_DECREF (tmpval );
1301
+ STACKLESS_PROMOTE_ALL ();
1302
+ #ifdef SLP_IMPL_THROW_BOMB_WITH_BOE
1303
+ ret = _impl_tasklet_throw_bomb (self , 0 , bomb , 1 );
1304
+ #else
1305
+ ret = _impl_tasklet_throw_bomb (self , 0 , bomb );
1306
+ #endif
1307
+ STACKLESS_ASSERT ();
1308
+
1215
1309
return ret ;
1216
1310
}
1217
1311
@@ -1256,20 +1350,38 @@ impl_tasklet_kill(PyTaskletObject *task, int pending)
1256
1350
STACKLESS_GETARG ();
1257
1351
PyObject * ret ;
1258
1352
1259
- /*
1260
- * silently do nothing if the tasklet is dead.
1261
- * simple raising would kill ourself in this case.
1353
+ /* We might be called without a thread state. If the tasklet
1354
+ * still has a frame, impl_tasklet_throw() will raise
1355
+ * RuntimeError. Therefore we need either to bind the tasklet to
1356
+ * a thread or drop its frames. Both makes sense, but the documentation
1357
+ * states, that Stackless does not silently change the thread of
1358
+ * a tasklet. Therefore we drop the frames.
1262
1359
*/
1263
- if (slp_get_frame (task ) == NULL ) {
1264
- /* it can still be a new tasklet and not a dead one */
1265
- Py_CLEAR (task -> f .cframe );
1266
- if (task -> next ) {
1267
- /* remove it from the run queue */
1268
- assert (!task -> flags .blocked );
1269
- slp_current_remove_tasklet (task );
1270
- Py_DECREF (task );
1360
+ assert (task -> cstate );
1361
+ if (task -> cstate -> tstate == NULL ) {
1362
+ #ifdef SLP_TASKLET_KILL_REBINDS_THREAD
1363
+ /* No thread state. Silently bind the tasklet to
1364
+ * the current thread or drop its frames.
1365
+ * Either action prevents an error in impl_tasklet_throw().
1366
+ */
1367
+ if (task -> cstate -> nesting_level == 0 && task -> f .frame ) {
1368
+ /* rebind to the current thread */
1369
+ PyObject * arg = PyTuple_New (0 );
1370
+ if (arg == NULL )
1371
+ return NULL ;
1372
+ ret = tasklet_bind_thread ((PyObject * )task , arg );
1373
+ Py_DECREF (arg );
1374
+ assert (ret != NULL ); /* should not fail, if nesting_level is 0 */
1375
+ if (ret == NULL ) /* in case of a bug */
1376
+ return NULL ;
1377
+ Py_DECREF (ret );
1378
+ } else {
1379
+ Py_CLEAR (task -> f .frame ); /* or better tasklet_clear_frames(task) ? */
1271
1380
}
1272
- Py_RETURN_NONE ;
1381
+ #else
1382
+ /* drop the frame */
1383
+ Py_CLEAR (task -> f .frame ); /* or better tasklet_clear_frames(task) ? */
1384
+ #endif
1273
1385
}
1274
1386
1275
1387
/* we might be called after exceptions are gone */
0 commit comments