From 998a2c3e3e1e9d4da4b67eb771a784e677ed3e1b Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Wed, 9 Sep 2009 23:50:06 +0200 Subject: [PATCH] Initial code for buffer locking. Add some buffer locking; does not yet actually work. Make current_buffer per-thread. Turn thread_state into a vectorlike. Arrange to populate initial specpdl; put condition-case around thread body. --- src/alloc.c | 7 ++-- src/buffer.c | 43 +++++++++++++++++--- src/buffer.h | 6 +-- src/eval.c | 10 ++--- src/image.c | 9 +++++ src/thread.c | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- src/thread.h | 25 ++++++++++-- src/window.c | 8 ++-- 8 files changed, 194 insertions(+), 43 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index d37295ec899..742c17a1b07 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -4484,8 +4484,9 @@ mark_stack (bottom, end) #endif /* GC_MARK_STACK != 0 */ void -flush_stack_call_func (func) - void (*func) P_ ((char *end)); +flush_stack_call_func (func, arg) + void (*func) P_ ((char *end, void *arg)); + void *arg; { #if GC_MARK_STACK /* jmp_buf may not be aligned enough on darwin-ppc64 */ @@ -4533,7 +4534,7 @@ flush_stack_call_func (func) #endif /* not GC_SAVE_REGISTERS_ON_STACK */ #endif /* GC_MARK_STACK != 0 */ - (*func) (end); + (*func) (end, arg); } diff --git a/src/buffer.c b/src/buffer.c index 62c7fad050f..10378c3edf4 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -37,6 +37,8 @@ extern int errno; #include #endif +#include + #include "lisp.h" #include "intervals.h" #include "window.h" @@ -50,8 +52,6 @@ extern int errno; #include "keymap.h" #include "frame.h" -struct buffer *current_buffer; /* the current buffer */ - /* First buffer in chain of all buffers (in reverse order of creation). Threaded through ->next. */ @@ -107,6 +107,10 @@ static char buffer_permanent_local_flags[MAX_PER_BUFFER_VARS]; int last_per_buffer_idx; +/* condition var .. w/ global lock */ + +static pthread_cond_t buffer_cond; + EXFUN (Fset_buffer, 1); void set_buffer_internal P_ ((struct buffer *b)); void set_buffer_internal_1 P_ ((struct buffer *b)); @@ -331,9 +335,6 @@ get_truename_buffer (filename) return Qnil; } -/* Incremented for each buffer created, to assign the buffer number. */ -int buffer_count; - DEFUN ("get-buffer-create", Fget_buffer_create, Sget_buffer_create, 1, 1, 0, doc: /* Return the buffer specified by BUFFER-OR-NAME, creating a new one if needed. If BUFFER-OR-NAME is a string and a live buffer with that name exists, @@ -1444,6 +1445,10 @@ with SIGHUP. */) if (NILP (b->name)) return Qnil; + tem = get_current_thread (); + if (!EQ (b->owner, Qnil) && !EQ (b->owner, tem)) + error ("Buffer locked by another thread"); + /* Query if the buffer is still modified. */ if (INTERACTIVE && !NILP (b->filename) && BUF_MODIFF (b) > BUF_SAVE_MODIFF (b)) @@ -1858,6 +1863,26 @@ set_buffer_internal (b) set_buffer_internal_1 (b); } +static void +acquire_buffer (char *end, void *nb) +{ + struct buffer *new_buffer = nb; + + /* FIXME this check should be in the caller, for better + single-threaded performance. */ + if (other_threads_p ()) + { + /* Let other threads try to acquire a buffer. */ + pthread_cond_broadcast (&buffer_cond); + + /* If our desired buffer is locked, wait for it. */ + while (!EQ (new_buffer->owner, Qnil) + /* We set the owner to Qt to mean it is being killed. */ + && !EQ (new_buffer->owner, Qt)) + pthread_cond_wait (&buffer_cond, &global_lock); + } +} + /* Set the current buffer to B, and do not set windows_or_buffers_changed. This is used by redisplay. */ @@ -1878,6 +1903,11 @@ set_buffer_internal_1 (b) return; old_buf = current_buffer; + if (current_buffer) + current_buffer->owner = Qnil; + flush_stack_call_func (acquire_buffer, b); + /* FIXME: if buffer is killed */ + b->owner = get_current_thread (); current_buffer = b; last_known_column_point = -1; /* invalidate indentation cache */ @@ -5210,6 +5240,7 @@ init_buffer_once () buffer_defaults.cursor_type = Qt; buffer_defaults.extra_line_spacing = Qnil; buffer_defaults.cursor_in_non_selected_windows = Qt; + buffer_defaults.owner = Qnil; #ifdef DOS_NT buffer_defaults.buffer_file_type = Qnil; /* TEXT */ @@ -5354,6 +5385,8 @@ init_buffer () Lisp_Object temp; int len; + pthread_cond_init (&buffer_cond, NULL); + #ifdef USE_MMAP_FOR_BUFFERS { /* When using the ralloc implementation based on mmap(2), buffer diff --git a/src/buffer.h b/src/buffer.h index b16fc4f6a68..85c78893d4e 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -800,12 +800,12 @@ struct buffer t means to use hollow box cursor. See `cursor-type' for other values. */ Lisp_Object cursor_in_non_selected_windows; + + /* If non-nil, the thread holding a lock on this buffer. */ + Lisp_Object owner; }; -/* This points to the current buffer. */ - -extern struct buffer *current_buffer; /* This structure holds the default values of the buffer-local variables that have special slots in each buffer. diff --git a/src/eval.c b/src/eval.c index 92b6b8adf8d..e0ab399caa3 100644 --- a/src/eval.c +++ b/src/eval.c @@ -3218,7 +3218,7 @@ DEFUN ("fetch-bytecode", Ffetch_bytecode, Sfetch_bytecode, return object; } -void +static void grow_specpdl () { register int count = SPECPDL_INDEX (); @@ -3268,14 +3268,14 @@ specbind (symbol, value) if (BUFFER_LOCAL_VALUEP (valcontents) || BUFFER_OBJFWDP (valcontents)) { - Lisp_Object where, current_buffer; + Lisp_Object where, self_buffer; - current_buffer = Fcurrent_buffer (); + self_buffer = Fcurrent_buffer (); /* For a local variable, record both the symbol and which buffer's or frame's value we are saving. */ if (!NILP (Flocal_variable_p (symbol, Qnil))) - where = current_buffer; + where = self_buffer; else if (BUFFER_LOCAL_VALUEP (valcontents) && XBUFFER_LOCAL_VALUE (valcontents)->found_for_frame) where = XBUFFER_LOCAL_VALUE (valcontents)->frame; @@ -3285,7 +3285,7 @@ specbind (symbol, value) /* We're not using the `unused' slot in the specbinding structure because this would mean we have to do more work for simple variables. */ - specpdl_ptr->symbol = Fcons (symbol, Fcons (where, current_buffer)); + specpdl_ptr->symbol = Fcons (symbol, Fcons (where, self_buffer)); /* If SYMBOL is a per-buffer variable which doesn't have a buffer-local value here, make the `let' change the global diff --git a/src/image.c b/src/image.c index 19aa8769283..4480cc52fbd 100644 --- a/src/image.c +++ b/src/image.c @@ -5555,6 +5555,15 @@ png_image_p (object) #ifdef HAVE_PNG +/* png.h has a struct with a field named current_buffer. */ +#undef current_buffer + +#if defined HAVE_LIBPNG_PNG_H +# include +#else +# include +#endif + #ifdef HAVE_NTGUI /* PNG library details. */ diff --git a/src/thread.c b/src/thread.c index 44ee3cd71ed..2f47c7e1037 100644 --- a/src/thread.c +++ b/src/thread.c @@ -7,7 +7,7 @@ void mark_byte_stack P_ ((struct byte_stack *)); void mark_backtrace P_ ((struct backtrace *)); void mark_catchlist P_ ((struct catchtag *)); void mark_stack P_ ((char *, char *)); -void flush_stack_call_func P_ ((void (*) (char *))); +void flush_stack_call_func P_ ((void (*) (char *, void *), void *)); static struct thread_state primary_thread; @@ -16,13 +16,14 @@ static struct thread_state *all_threads = &primary_thread; __thread struct thread_state *current_thread = &primary_thread; -static pthread_mutex_t global_lock; +pthread_mutex_t global_lock; static void mark_one_thread (struct thread_state *thread) { register struct specbinding *bind; struct handler *handler; + Lisp_Object tem; for (bind = thread->m_specpdl; bind != thread->m_specpdl_ptr; bind++) { @@ -54,24 +55,29 @@ mark_one_thread (struct thread_state *thread) mark_backtrace (thread->m_backtrace_list); - if (thread->func) - mark_object (thread->func); + XSETBUFFER (tem, thread->m_current_buffer); + mark_object (tem); } static void -mark_threads_continuation (char *end) +mark_threads_callback (char *end, void *ignore) { struct thread_state *iter; current_thread->stack_top = end; - for (iter = all_threads; iter; iter = iter->next) - mark_one_thread (iter); + for (iter = all_threads; iter; iter = iter->next_thread) + { + Lisp_Object thread_obj; + XSETTHREAD (thread_obj, iter); + mark_object (thread_obj); + mark_one_thread (iter); + } } void mark_threads (void) { - flush_stack_call_func (mark_threads_continuation); + flush_stack_call_func (mark_threads_callback, NULL); } void @@ -79,12 +85,12 @@ unmark_threads (void) { struct thread_state *iter; - for (iter = all_threads; iter; iter = iter->next) + for (iter = all_threads; iter; iter = iter->next_thread) unmark_byte_stack (iter->m_byte_stack_list); } static void -thread_yield_continuation (char *end) +thread_yield_callback (char *end, void *ignore) { current_thread->stack_top = end; pthread_mutex_unlock (&global_lock); @@ -98,8 +104,8 @@ thread_yield (void) /* Note: currently it is safe to check this here, but eventually it will require a lock to ensure non-racy operation. */ /* Only yield if there is another thread to yield to. */ - if (all_threads->next) - flush_stack_call_func (thread_yield_continuation); + if (all_threads->next_thread) + flush_stack_call_func (thread_yield_callback, NULL); } DEFUN ("yield", Fyield, Syield, 0, 0, 0, @@ -109,12 +115,43 @@ DEFUN ("yield", Fyield, Syield, 0, 0, 0, thread_yield (); } +static Lisp_Object +invoke_thread_function (void) +{ + Lisp_Object iter; + + int count = SPECPDL_INDEX (); + + /* Set up specpdl. */ + for (iter = current_thread->initial_specpdl; + !EQ (iter, Qnil); + iter = XCDR (iter)) + { + /* We may bind a variable twice -- but it doesn't matter because + there is no way to undo these bindings without exiting the + thread. */ + specbind (XCAR (XCAR (iter)), XCDR (XCAR (iter))); + } + current_thread->initial_specpdl = Qnil; + + Ffuncall (1, ¤t_thread->func); + return unbind_to (count, Qnil); +} + +static Lisp_Object +do_nothing (Lisp_Object whatever) +{ + return whatever; +} + static void * run_thread (void *state) { char stack_bottom_variable; struct thread_state *self = state; struct thread_state **iter; + struct gcpro gcpro1; + Lisp_Object buffer; self->stack_bottom = &stack_bottom_variable; @@ -128,16 +165,23 @@ run_thread (void *state) pthread_mutex_lock (&global_lock); - /* FIXME: unwind protect here. */ - Ffuncall (1, &self->func); + /* We need special handling to set the initial buffer. Our parent + thread is very likely to be using this same buffer so we will + typically wait for the parent thread to release it first. */ + XSETBUFFER (buffer, self->m_current_buffer); + GCPRO1 (buffer); + self->m_current_buffer = 0; + set_buffer_internal (XBUFFER (buffer)); + + /* It might be nice to do something with errors here. */ + internal_condition_case (invoke_thread_function, Qt, do_nothing); /* Unlink this thread from the list of all threads. */ - for (iter = &all_threads; *iter != self; iter = &(*iter)->next) + for (iter = &all_threads; *iter != self; iter = &(*iter)->next_thread) ; - *iter = (*iter)->next; + *iter = (*iter)->next_thread; xfree (self->m_specpdl); - /* FIXME: other cleanups here. */ xfree (self); pthread_mutex_unlock (&global_lock); @@ -153,22 +197,67 @@ When the function exits, the thread dies. */) { pthread_t thr; struct thread_state *new_thread; + struct specbinding *p; /* Can't start a thread in temacs. */ if (!initialized) abort (); - new_thread = xmalloc (sizeof (struct thread_state)); - memset (new_thread, 0, sizeof (struct thread_state)); + new_thread = (struct thread_state *) allocate_pseudovector (VECSIZE (struct thread_state), + 2, PVEC_THREAD); + memset (new_thread, OFFSETOF (struct thread_state, + m_gcprolist), + sizeof (struct thread_state) - OFFSETOF (struct thread_state, + m_gcprolist)); new_thread->func = function; + new_thread->initial_specpdl = Qnil; + + for (p = specpdl; p != specpdl_ptr; ++p) + { + if (p->func) + { + Lisp_Object sym = p->symbol; + if (!SYMBOLP (sym)) + sym = XCAR (sym); + new_thread->initial_specpdl + = Fcons (Fcons (sym, find_symbol_value (sym)), + new_thread->initial_specpdl); + } + } /* We'll need locking here. */ - new_thread->next = all_threads; + new_thread->next_thread = all_threads; all_threads = new_thread; /* FIXME check result */ pthread_create (&thr, NULL, run_thread, new_thread); + + return Qnil; +} + +/* Get the current thread as a lisp object. */ +Lisp_Object +get_current_thread (void) +{ + Lisp_Object result; + XSETTHREAD (result, current_thread); + return result; +} + +/* Get the main thread as a lisp object. */ +Lisp_Object +get_main_thread (void) +{ + Lisp_Object result; + XSETTHREAD (result, &primary_thread); + return result; +} + +int +other_threads_p (void) +{ + return all_threads->next_thread != NULL; } void diff --git a/src/thread.h b/src/thread.h index a415727fab2..8f7b02ba2da 100644 --- a/src/thread.h +++ b/src/thread.h @@ -1,6 +1,16 @@ struct thread_state { + EMACS_UINT size; + struct Lisp_Vector *next; + + /* The function we are evaluating, or 0 in the main thread. */ + Lisp_Object func; + + /* An alias of symbols and values that we use to populate the + initial specpdl. */ + Lisp_Object initial_specpdl; + /* Recording what needs to be marked for gc. */ struct gcpro *m_gcprolist; #define gcprolist (current_thread->m_gcprolist) @@ -54,10 +64,11 @@ struct thread_state int m_lisp_eval_depth; #define lisp_eval_depth (current_thread->m_lisp_eval_depth) - /* The function we are evaluating, or 0 in the main thread. */ - Lisp_Object func; + /* This points to the current buffer. */ + struct buffer *m_current_buffer; +#define current_buffer (current_thread->m_current_buffer) - struct thread_state *next; + struct thread_state *next_thread; }; extern __thread struct thread_state *current_thread; @@ -67,3 +78,11 @@ extern void init_threads P_ ((void)); extern void thread_yield P_ ((void)); extern void syms_of_threads P_ ((void)); + +extern Lisp_Object get_current_thread P_ ((void)); + +extern Lisp_Object get_main_thread P_ ((void)); + +extern pthread_mutex_t global_lock; + +extern int other_threads_p P_ ((void)); diff --git a/src/window.c b/src/window.c index a3964f3fb74..60b50e0998f 100644 --- a/src/window.c +++ b/src/window.c @@ -5915,7 +5915,7 @@ struct save_window_data struct Lisp_Vector *next_from_Lisp_Vector_struct; Lisp_Object selected_frame; Lisp_Object current_window; - Lisp_Object current_buffer; + Lisp_Object m_current_buffer; Lisp_Object minibuf_scroll_window; Lisp_Object minibuf_selected_window; Lisp_Object root_window; @@ -6001,7 +6001,7 @@ the return value is nil. Otherwise the value is t. */) data = (struct save_window_data *) XVECTOR (configuration); saved_windows = XVECTOR (data->saved_windows); - new_current_buffer = data->current_buffer; + new_current_buffer = data->m_current_buffer; if (NILP (XBUFFER (new_current_buffer)->name)) new_current_buffer = Qnil; else @@ -6526,7 +6526,7 @@ redirection (see `redirect-frame-focus'). */) data->frame_tool_bar_lines = FRAME_TOOL_BAR_LINES (f); data->selected_frame = selected_frame; data->current_window = FRAME_SELECTED_WINDOW (f); - XSETBUFFER (data->current_buffer, current_buffer); + XSETBUFFER (data->m_current_buffer, current_buffer); data->minibuf_scroll_window = minibuf_level > 0 ? Vminibuf_scroll_window : Qnil; data->minibuf_selected_window = minibuf_level > 0 ? minibuf_selected_window : Qnil; data->root_window = FRAME_ROOT_WINDOW (f); @@ -7047,7 +7047,7 @@ compare_window_configurations (c1, c2, ignore_positions) return 0; /* Don't compare the current_window field directly. Instead see w1_is_current and w2_is_current, below. */ - if (! EQ (d1->current_buffer, d2->current_buffer)) + if (! EQ (d1->m_current_buffer, d2->m_current_buffer)) return 0; if (! ignore_positions) { -- 2.11.4.GIT