From a96720907a040a331f4a75c254a6172daa7273e7 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 14 Sep 1994 13:31:22 +0000 Subject: [PATCH] * Python/ceval.c (eval_code): added registry of pending functions (to be used by functions that are called asynchronously, like UNIX signal handlers or Mac I/O completion routines) --- Python/ceval.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/Python/ceval.c b/Python/ceval.c index 829e7526aa6..2cc5f97d1be 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -104,7 +104,8 @@ static frameobject *current_frame; #include #include "thread.h" -static type_lock interpreter_lock; +static type_lock interpreter_lock = 0; +static long main_thread = 0; void init_save_thread() @@ -113,6 +114,7 @@ init_save_thread() return; interpreter_lock = allocate_lock(); acquire_lock(interpreter_lock, 1); + main_thread = get_thread_ident(); } #endif @@ -152,6 +154,87 @@ restore_thread(x) } +/* Mechanism whereby asynchronously executing callbacks (e.g. UNIX + signal handlers or Mac I/O completion routines) can schedule calls + to a function to be called synchronously. + The synchronous function is called with one void* argument. + It should return 0 for success or -1 for failure -- failure should + be accompanied by an exception. + + If registry succeeds, the registry function returns 0; if it fails + (e.g. due to too many pending calls) it returns -1 (without setting + an exception condition). + + Note that because registry may occur from within signal handlers, + or other asynchronous events, calling malloc() is unsafe! + +#ifdef WITH_THREAD + Any thread can schedule pending calls, but only the main thread + will execute them. +#endif + + XXX WARNING! ASYNCHRONOUSLY EXECUTING CODE! + There are two possible race conditions: + (1) nested asynchronous registry calls; + (2) registry calls made while pending calls are being processed. + While (1) is very unlikely, (2) is a real possibility. + The current code is safe against (2), but not against (1). + The safety against (2) is derived from the fact that only one + thread (the main thread) ever takes things out of the queue. +*/ + +#define NPENDINGCALLS 32 +static struct { + int (*func) PROTO((ANY *)); + ANY *arg; +} pendingcalls[NPENDINGCALLS]; +static volatile int pendingfirst = 0; +static volatile int pendinglast = 0; + +int +Py_AddPendingCall(func, arg) + int (*func) PROTO((ANY *)); + ANY *arg; +{ + int i, j; + /* XXX Begin critical section */ + /* XXX If you want this to be safe against nested + XXX asynchronous calls, you'll have to work harder! */ + i = pendinglast; + j = (i + 1) % NPENDINGCALLS; + if (j == pendingfirst) + return -1; /* Queue full */ + pendingcalls[i].func = func; + pendingcalls[i].arg = arg; + pendinglast = j; + /* XXX End critical section */ + return 0; +} + +static int +MakePendingCalls() +{ +#ifdef WITH_THREAD + if (get_thread_ident() != main_thread) + return 0; +#endif + for (;;) { + int i; + int (*func) PROTO((ANY *)); + ANY *arg; + i = pendingfirst; + if (i == pendinglast) + break; /* Queue empty */ + func = pendingcalls[i].func; + arg = pendingcalls[i].arg; + pendingfirst = (i + 1) % NPENDINGCALLS; + if (func(arg) < 0) + return -1; + } + return 0; +} + + /* Status code for main loop (reason for stack unwind) */ enum why_code { @@ -314,6 +397,13 @@ eval_code(co, globals, locals, owner, arg) too much overhead (a function call per instruction). So we do it only every Nth instruction. */ + if (pendingfirst != pendinglast) { + if (MakePendingCalls() < 0) { + why = WHY_EXCEPTION; + goto on_error; + } + } + if (--ticker < 0) { ticker = ticker_count; if (sigcheck()) {