mirror of
https://github.com/python/cpython.git
synced 2025-07-23 03:05:38 +00:00
An Anonymous Coward on c.l.py posted a little program with bizarre
behavior, creating many threads very quickly. A long debugging session revealed that the Windows implementation of PyThread_start_new_thread() was choked with "laziness" errors: 1. It checked MS _beginthread() for a failure return, but when that happened it returned heap trash as the function result, instead of an id of -1 (the proper error-return value). 2. It didn't consider that the Win32 CreateSemaphore() can fail. 3. When creating a great many threads very quickly, it's quite possible that any particular bootstrap call can take virtually any amount of time to return. But the code waited for a maximum of 5 seconds, and didn't check to see whether the semaphore it was waiting for got signaled. If it in fact timed out, the function could again return heap trash as the function result. This is actually what confused the test program, as the heap trash usually turned out to be 0, and then multiple threads all got id 0 simultaneously, confusing the hell out of threading.py's _active dict (mapping id to thread object). A variety of baffling behaviors followed from that. WRT #1 and #2, error returns are checked now, and "thread.error: can't start new thread" gets raised now if a new thread (or new semaphore) can't be created. WRT #3, we now wait for the semaphore without a timeout. Also removed useless local vrbls, folded long lines, and changed callobj to a stack auto (it was going thru malloc/free instead, for no discernible reason). Bugfix candidate.
This commit is contained in:
parent
75132e84e1
commit
2e7e7df969
2 changed files with 40 additions and 22 deletions
11
Misc/NEWS
11
Misc/NEWS
|
@ -10,6 +10,17 @@ What's New in Python 2.3 release candidate?
|
||||||
Core and builtins
|
Core and builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- The Windows implementation of PyThread_start_new_thread() never
|
||||||
|
checked error returns from Windows functions correctly. As a result,
|
||||||
|
it could claim to start a new thread even when the Microsoft
|
||||||
|
_beginthread() function failed (due to "too many threads" -- this is
|
||||||
|
on the order of thousands when it happens). In these cases, the
|
||||||
|
Python exception ::
|
||||||
|
|
||||||
|
thread.error: can't start new thread
|
||||||
|
|
||||||
|
is raised now.
|
||||||
|
|
||||||
Extension modules
|
Extension modules
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ static void PyThread__init_thread(void)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void (*func)(void*);
|
void (*func)(void*);
|
||||||
void *arg;
|
void *arg;
|
||||||
long id;
|
long id;
|
||||||
HANDLE done;
|
HANDLE done;
|
||||||
} callobj;
|
} callobj;
|
||||||
|
@ -167,35 +167,42 @@ bootstrap(void *call)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
long PyThread_start_new_thread(void (*func)(void *), void *arg)
|
long
|
||||||
|
PyThread_start_new_thread(void (*func)(void *), void *arg)
|
||||||
{
|
{
|
||||||
unsigned long rv;
|
unsigned long rv;
|
||||||
int success = 0;
|
callobj obj;
|
||||||
callobj *obj;
|
|
||||||
int id;
|
|
||||||
|
|
||||||
dprintf(("%ld: PyThread_start_new_thread called\n", PyThread_get_thread_ident()));
|
dprintf(("%ld: PyThread_start_new_thread called\n",
|
||||||
|
PyThread_get_thread_ident()));
|
||||||
if (!initialized)
|
if (!initialized)
|
||||||
PyThread_init_thread();
|
PyThread_init_thread();
|
||||||
|
|
||||||
obj = malloc(sizeof(callobj));
|
obj.id = -1; /* guilty until proved innocent */
|
||||||
obj->func = func;
|
obj.func = func;
|
||||||
obj->arg = arg;
|
obj.arg = arg;
|
||||||
obj->done = CreateSemaphore(NULL, 0, 1, NULL);
|
obj.done = CreateSemaphore(NULL, 0, 1, NULL);
|
||||||
|
if (obj.done == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
rv = _beginthread(bootstrap, 0, obj); /* use default stack size */
|
rv = _beginthread(bootstrap, 0, &obj); /* use default stack size */
|
||||||
|
if (rv == (unsigned long)-1) {
|
||||||
if (rv != (unsigned long)-1) {
|
/* I've seen errno == EAGAIN here, which means "there are
|
||||||
success = 1;
|
* too many threads".
|
||||||
dprintf(("%ld: PyThread_start_new_thread succeeded: %p\n", PyThread_get_thread_ident(), rv));
|
*/
|
||||||
|
dprintf(("%ld: PyThread_start_new_thread failed: %p errno %d\n",
|
||||||
|
PyThread_get_thread_ident(), rv, errno));
|
||||||
|
obj.id = -1;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
/* wait for thread to initialize and retrieve id */
|
dprintf(("%ld: PyThread_start_new_thread succeeded: %p\n",
|
||||||
WaitForSingleObject(obj->done, 5000); /* maybe INFINITE instead of 5000? */
|
PyThread_get_thread_ident(), rv));
|
||||||
CloseHandle((HANDLE)obj->done);
|
/* wait for thread to initialize, so we can get its id */
|
||||||
id = obj->id;
|
WaitForSingleObject(obj.done, INFINITE);
|
||||||
free(obj);
|
assert(obj.id != -1);
|
||||||
return id;
|
}
|
||||||
|
CloseHandle((HANDLE)obj.done);
|
||||||
|
return obj.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue