@@ -1077,39 +1077,114 @@ PyTasklet_Throw_M(PyTaskletObject *self, int pending, PyObject *exc,
10771077}
10781078
10791079static 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
10811087{
10821088 STACKLESS_GETARG ();
10831089 PyThreadState * ts = PyThreadState_GET ();
1084- PyObject * ret , * bomb , * tmpval ;
1090+ PyObject * ret , * tmpval ;
10851091 int fail ;
10861092
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 ));
10941095 /* raise it directly if target is ourselves. delayed exception makes
10951096 * no sense in this case
10961097 */
1097- if (ts -> st .current == self )
1098+ if (ts -> st .current == self ) {
1099+ assert (self -> cstate -> tstate == ts );
10981100 return slp_bomb_explode (bomb );
1101+ }
10991102
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.
11031104 */
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
11071119 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 );
11091146 }
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
11101184 Py_DECREF (bomb );
1111- RUNTIME_ERROR ("You cannot throw to a dead tasklet " , NULL );
1185+ RUNTIME_ERROR ("Target thread isn't initialised " , NULL );
11121186 }
1187+
11131188 TASKLET_CLAIMVAL (self , & tmpval );
11141189 TASKLET_SETVAL_OWN (self , bomb );
11151190 if (!pending ) {
@@ -1141,6 +1216,30 @@ impl_tasklet_throw(PyTaskletObject *self, int pending, PyObject *exc, PyObject *
11411216 return ret ;
11421217}
11431218
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+
11441243int PyTasklet_Throw (PyTaskletObject * self , int pending , PyObject * exc ,
11451244 PyObject * val , PyObject * tb )
11461245{
@@ -1191,27 +1290,22 @@ impl_tasklet_raise_exception(PyTaskletObject *self, PyObject *klass, PyObject *a
11911290{
11921291 STACKLESS_GETARG ();
11931292 PyThreadState * ts = PyThreadState_GET ();
1194- PyObject * ret , * bomb , * tmpval ;
1195- int fail ;
1293+ PyObject * ret , * bomb ;
11961294
11971295 if (ts -> st .main == NULL )
11981296 return PyTasklet_RaiseException_M (self , klass , args );
11991297 bomb = slp_make_bomb (klass , args , "tasklet.raise_exception" );
12001298 if (bomb == NULL )
12011299 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 );
12071300
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+
12151309 return ret ;
12161310}
12171311
@@ -1256,20 +1350,38 @@ impl_tasklet_kill(PyTaskletObject *task, int pending)
12561350 STACKLESS_GETARG ();
12571351 PyObject * ret ;
12581352
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.
12621359 */
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) ? */
12711380 }
1272- Py_RETURN_NONE ;
1381+ #else
1382+ /* drop the frame */
1383+ Py_CLEAR (task -> f .frame ); /* or better tasklet_clear_frames(task) ? */
1384+ #endif
12731385 }
12741386
12751387 /* we might be called after exceptions are gone */
0 commit comments