@@ -209,29 +209,41 @@ static int nkeys = 0; /* PyThread_create_key() hands out nkeys+1 next */
209209 * So when value==NULL, this acts like a pure lookup routine, and when
210210 * value!=NULL, this acts like dict.setdefault(), returning an existing
211211 * mapping if one exists, else creating a new mapping.
212+ *
213+ * Caution: this used to be too clever, trying to hold keymutex only
214+ * around the "p->next = keyhead; keyhead = p" pair. That allowed
215+ * another thread to mutate the list, via key deletion, concurrent with
216+ * find_key() crawling over the list. Hilarity ensued. For example, when
217+ * the for-loop here does "p = p->next", p could end up pointing at a
218+ * record that PyThread_delete_key_value() was concurrently free()'ing.
219+ * That could lead to anything, from failing to find a key that exists, to
220+ * segfaults. Now we lock the whole routine.
212221 */
213222static struct key *
214223find_key (int key , void * value )
215224{
216225 struct key * p ;
217226 long id = PyThread_get_thread_ident ();
218227
228+ PyThread_acquire_lock (keymutex , 1 );
219229 for (p = keyhead ; p != NULL ; p = p -> next ) {
220230 if (p -> id == id && p -> key == key )
221- return p ;
231+ goto Done ;
232+ }
233+ if (value == NULL ) {
234+ assert (p == NULL );
235+ goto Done ;
222236 }
223- if (value == NULL )
224- return NULL ;
225237 p = (struct key * )malloc (sizeof (struct key ));
226238 if (p != NULL ) {
227239 p -> id = id ;
228240 p -> key = key ;
229241 p -> value = value ;
230- PyThread_acquire_lock (keymutex , 1 );
231242 p -> next = keyhead ;
232243 keyhead = p ;
233- PyThread_release_lock (keymutex );
234244 }
245+ Done :
246+ PyThread_release_lock (keymutex );
235247 return p ;
236248}
237249
0 commit comments