diff Python/thread.c @ 33950:633be43bed17 2.3

Backported thread fixes from 2.4 (by mostly copying pystate.c over from 2.4): * using malloc() and free() directly, as explained in the new comment * coding style in the PyGILState_*() functions * the recent destroy-tstate-without-holding-the-GIL bug * lock fixes and many more comments in thread.c
author Armin Rigo <arigo@tunes.org>
date Tue, 09 Nov 2004 15:35:23 +0000
parents 1a817694ecbd
children
line wrap: on
line diff
--- a/Python/thread.c	Sat Nov 06 00:14:05 2004 +0000
+++ b/Python/thread.c	Tue Nov 09 15:35:23 2004 +0000
@@ -146,54 +146,132 @@
    This code stolen from "thread_sgi.h", where it was the only
    implementation of an existing Python TLS API.
 */
-/*
- * Per-thread data ("key") support.
- */
+/* ------------------------------------------------------------------------
+Per-thread data ("key") support.
+
+Use PyThread_create_key() to create a new key.  This is typically shared
+across threads.
+
+Use PyThread_set_key_value(thekey, value) to associate void* value with
+thekey in the current thread.  Each thread has a distinct mapping of thekey
+to a void* value.  Caution:  if the current thread already has a mapping
+for thekey, value is ignored.
+
+Use PyThread_get_key_value(thekey) to retrieve the void* value associated
+with thekey in the current thread.  This returns NULL if no value is
+associated with thekey in the current thread.
+
+Use PyThread_delete_key_value(thekey) to forget the current thread's associated
+value for thekey.  PyThread_delete_key(thekey) forgets the values associated
+with thekey across *all* threads.
+
+While some of these functions have error-return values, none set any
+Python exception.
 
+None of the functions does memory management on behalf of the void* values.
+You need to allocate and deallocate them yourself.  If the void* values
+happen to be PyObject*, these functions don't do refcount operations on
+them either.
+
+The GIL does not need to be held when calling these functions; they supply
+their own locking.  This isn't true of PyThread_create_key(), though (see
+next paragraph).
+
+There's a hidden assumption that PyThread_create_key() will be called before
+any of the other functions are called.  There's also a hidden assumption
+that calls to PyThread_create_key() are serialized externally.
+------------------------------------------------------------------------ */
+
+/* A singly-linked list of struct key objects remembers all the key->value
+ * associations.  File static keyhead heads the list.  keymutex is used
+ * to enforce exclusion internally.
+ */
 struct key {
+	/* Next record in the list, or NULL if this is the last record. */
 	struct key *next;
+
+	/* The thread id, according to PyThread_get_thread_ident(). */
 	long id;
+
+	/* The key and its associated value. */
 	int key;
 	void *value;
 };
 
 static struct key *keyhead = NULL;
-static int nkeys = 0;
 static PyThread_type_lock keymutex = NULL;
+static int nkeys = 0;  /* PyThread_create_key() hands out nkeys+1 next */
 
-static struct key *find_key(int key, void *value)
+/* Internal helper.
+ * If the current thread has a mapping for key, the appropriate struct key*
+ * is returned.  NB:  value is ignored in this case!
+ * If there is no mapping for key in the current thread, then:
+ *     If value is NULL, NULL is returned.
+ *     Else a mapping of key to value is created for the current thread,
+ *     and a pointer to a new struct key* is returned; except that if
+ *     malloc() can't find room for a new struct key*, NULL is returned.
+ * So when value==NULL, this acts like a pure lookup routine, and when
+ * value!=NULL, this acts like dict.setdefault(), returning an existing
+ * mapping if one exists, else creating a new mapping.
+ *
+ * Caution:  this used to be too clever, trying to hold keymutex only
+ * around the "p->next = keyhead; keyhead = p" pair.  That allowed
+ * another thread to mutate the list, via key deletion, concurrent with
+ * find_key() crawling over the list.  Hilarity ensued.  For example, when
+ * the for-loop here does "p = p->next", p could end up pointing at a
+ * record that PyThread_delete_key_value() was concurrently free()'ing.
+ * That could lead to anything, from failing to find a key that exists, to
+ * segfaults.  Now we lock the whole routine.
+ */
+static struct key *
+find_key(int key, void *value)
 {
 	struct key *p;
 	long id = PyThread_get_thread_ident();
+
+	PyThread_acquire_lock(keymutex, 1);
 	for (p = keyhead; p != NULL; p = p->next) {
 		if (p->id == id && p->key == key)
-			return p;
+			goto Done;
 	}
-	if (value == NULL)
-		return NULL;
+	if (value == NULL) {
+		assert(p == NULL);
+		goto Done;
+	}
 	p = (struct key *)malloc(sizeof(struct key));
 	if (p != NULL) {
 		p->id = id;
 		p->key = key;
 		p->value = value;
-		PyThread_acquire_lock(keymutex, 1);
 		p->next = keyhead;
 		keyhead = p;
-		PyThread_release_lock(keymutex);
 	}
+ Done:
+	PyThread_release_lock(keymutex);
 	return p;
 }
 
-int PyThread_create_key(void)
+/* Return a new key.  This must be called before any other functions in
+ * this family, and callers must arrange to serialize calls to this
+ * function.  No violations are detected.
+ */
+int
+PyThread_create_key(void)
 {
+	/* All parts of this function are wrong if it's called by multiple
+	 * threads simultaneously.
+	 */
 	if (keymutex == NULL)
 		keymutex = PyThread_allocate_lock();
 	return ++nkeys;
 }
 
-void PyThread_delete_key(int key)
+/* Forget the associations for key across *all* threads. */
+void
+PyThread_delete_key(int key)
 {
 	struct key *p, **q;
+
 	PyThread_acquire_lock(keymutex, 1);
 	q = &keyhead;
 	while ((p = *q) != NULL) {
@@ -208,28 +286,46 @@
 	PyThread_release_lock(keymutex);
 }
 
-int PyThread_set_key_value(int key, void *value)
+/* Confusing:  If the current thread has an association for key,
+ * value is ignored, and 0 is returned.  Else an attempt is made to create
+ * an association of key to value for the current thread.  0 is returned
+ * if that succeeds, but -1 is returned if there's not enough memory
+ * to create the association.  value must not be NULL.
+ */
+int
+PyThread_set_key_value(int key, void *value)
 {
-	struct key *p = find_key(key, value);
+	struct key *p;
+
+	assert(value != NULL);
+	p = find_key(key, value);
 	if (p == NULL)
 		return -1;
 	else
 		return 0;
 }
 
-void *PyThread_get_key_value(int key)
+/* Retrieve the value associated with key in the current thread, or NULL
+ * if the current thread doesn't have an association for key.
+ */
+void *
+PyThread_get_key_value(int key)
 {
 	struct key *p = find_key(key, NULL);
+
 	if (p == NULL)
 		return NULL;
 	else
 		return p->value;
 }
 
-void PyThread_delete_key_value(int key)
+/* Forget the current thread's association for key, if any. */
+void
+PyThread_delete_key_value(int key)
 {
 	long id = PyThread_get_thread_ident();
 	struct key *p, **q;
+
 	PyThread_acquire_lock(keymutex, 1);
 	q = &keyhead;
 	while ((p = *q) != NULL) {