On Fri, Jun 15, 2007 at 05:26:41PM -0700, Daniel Burrows <[EMAIL PROTECTED]> was heard to say: > So, I need to find a way to do wget_wch() in my main thread, while > still unblocking when a background thread posts a message to my global > queue. I guess this means I have to make some sort of dummy > pipe-to-myself so I can select() on stdin and it (ew ew ew)...except, > does wget_wch always return on the first character? What about > multibyte input? The wget_wch manpage is, as usual, silent...
OK, after getting some sleep I realized that it's not that bad. The attached patch at least fixes the problem I saw in strace, although I can't reproduce the bug on my dual-core system at the moment (possibly because I'm running xen vms on it?). Daniel
Sat Jun 16 09:42:58 PDT 2007 Daniel Burrows <[EMAIL PROTECTED]> * Rewrite the input thread to eliminate a race condition. (Closes: #414838, #406193) The problem here is that get_wch() and its friend getch() both implicitly invoke refresh(). So instead of calling get_wch() from the background thread, I select() on fd 0, then spawn an event that causes the foreground thread to run get_wch() and dispatch the result. The tricky bit is that obviously I don't want to keep selecting and posting events, so I have synchronization to block the background thread until the event completes. Sat Jun 16 09:42:32 PDT 2007 Daniel Burrows <[EMAIL PROTECTED]> * Use the new bootstrap proxy code to simplify the timeout thread. Sat Jun 16 09:41:53 PDT 2007 Daniel Burrows <[EMAIL PROTECTED]> * Add utility code in threads:: to make it easier to create proxies for uncopyable thread bootstrap functions. diff -rN -u old-head/src/generic/util/threads.h new-head/src/generic/util/threads.h --- old-head/src/generic/util/threads.h 2007-06-16 10:35:46.000000000 -0700 +++ new-head/src/generic/util/threads.h 2007-06-16 10:35:47.000000000 -0700 @@ -1,6 +1,6 @@ // threads.h -*-c++-*- // -// Copyright (C) 2005-2006 Daniel Burrows +// Copyright (C) 2005-2007 Daniel Burrows // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -912,6 +912,32 @@ return b.timed_put(in, until); } }; + + // A utility that proxies for noncopyable thread bootstrap + // objects. The only requirement is that the pointer passed + // to the constructor must not be destroyed until the thread + // completes. + template<typename F> + class bootstrap_proxy + { + F *f; + public: + bootstrap_proxy(F *_f) + : f(_f) + { + } + + void operator()() const + { + (*f)(); + } + }; + + template<typename F> + bootstrap_proxy<F> make_bootstrap_proxy(F *f) + { + return bootstrap_proxy<F>(f); + } } #endif // THREADS_H diff -rN -u old-head/src/vscreen/vscreen.cc new-head/src/vscreen/vscreen.cc --- old-head/src/vscreen/vscreen.cc 2007-06-16 10:35:46.000000000 -0700 +++ new-head/src/vscreen/vscreen.cc 2007-06-16 10:35:46.000000000 -0700 @@ -43,6 +43,7 @@ #include "config/style.h" #include <generic/util/event_queue.h> +#include <generic/util/util.h> #include <generic/util/threads.h> // For _() @@ -201,55 +202,135 @@ ////////////////////////////////////////////////////////////////////// // Event management threads -/** This thread is responsible for posting wget_wch() calls. */ +/** This thread is responsible for posting wget_wch() calls. + * + * Note that the actual call to wget_wch must take place in the + * foreground thread, because wget_wch will invoke wrefresh(). + * So instead of calling it in the background thread, I post + * input events to the foreground thread. + * + * To prevent the background thread from spamming the foreground + * thread with events, I suspend it until the event actually + * triggers. + */ class input_thread { - class key_input_event : public vscreen_event + class get_input_event : public vscreen_event { - key k; + // A reference to the parent's condition mutex; + // should be held while we signal the condition. + threads::mutex &m; + // A reference to the parent's variable indicating + // whether the event has triggered. Will be set + // to "true" after we try to read all available + // keystrokes. + bool &b; + // A reference to the parent's condition variable. + threads::condition &c; + public: - key_input_event (const key &_k) - :k(_k) + get_input_event(threads::mutex &_m, bool &_b, threads::condition &_c) + : m(_m), b(_b), c(_c) { } void dispatch() { - if(global_bindings.key_matches(k, "Refresh")) - vscreen_redraw(); - else - toplevel->dispatch_key(k); + // NB: use the GLOBAL getch function to avoid weirdness + // referencing the state of toplevel. + wint_t wch = 0; + int status; + + bool done = false; + + while(!done) + { + // I assume here that vscreen_init() set nodelay. + do + { + status = get_wch(&wch); + } while(status == KEY_CODE_YES && wch == KEY_RESIZE); + + if(status == ERR) // No more to read. + { + threads::mutex::lock l(m); + b = true; + c.wake_all(); + done = true; + } + else + { + key k(wch, status == KEY_CODE_YES); + + if(wch == KEY_MOUSE) + { + if(toplevel.valid()) + { + MEVENT ev; + getmouse(&ev); + + toplevel->dispatch_mouse(ev.id, ev.x, ev.y, ev.z, ev.bstate); + } + } + else + { + if(global_bindings.key_matches(k, "Refresh")) + vscreen_redraw(); + else + toplevel->dispatch_key(k); + } + } + } } }; - class mouse_input_event : public vscreen_event + class fatal_input_exception : public Exception { - MEVENT ev; + int err; + public: + fatal_input_exception(int _err) + : err(_err) + { + } + std::string errmsg() const + { + return "Unable to read from stdin: " + sstrerror(err); + } + }; + + class fatal_input_error : public vscreen_event + { + int err; public: - mouse_input_event(const MEVENT &_ev) - :ev(_ev) + fatal_input_error(int _err) + : err(_err) { } void dispatch() { - if(toplevel.valid()) - toplevel->dispatch_mouse(ev.id, ev.x, ev.y, ev.z, ev.bstate); + throw new fatal_input_exception(err); } }; + threads::mutex input_event_mutex; + // Used to block this thread until an event to read input fires. + bool input_event_fired; + threads::condition input_event_condition; + static input_thread instance; static threads::mutex instance_mutex; static threads::thread *instancet; + public: static void start() { threads::mutex::lock l(instance_mutex); if(instancet == NULL) - instancet = new threads::thread(instance); + instancet = new threads::thread(threads::make_bootstrap_proxy(&instance)); } static void stop() @@ -265,34 +346,48 @@ } } - void operator()() const + void operator()() { - // NB: use the GLOBAL getch function to avoid weirdness - // referencing the state of toplevel. - while(1) - { - wint_t wch = 0; - int status; + threads::mutex::lock l(input_event_mutex); + input_event_fired = false; - do - { - status = get_wch(&wch); - } while(status == KEY_CODE_YES && wch == KEY_RESIZE); + // Important note: this routine only blocks indefinitely in + // select() and pthread_cond_wait(), assuming no bugs in + // vscreen_post_event(). pthread_cond_wait() is a cancellation + // point. select() should be but isn't, but there is a + // workaround below. - key k(wch, status == KEY_CODE_YES); - - if(status == ERR) - return; // ??? + while(1) + { + // Select on stdin; when we see that input is available, spawn + // an input event. + fd_set selectfds; + FD_ZERO(&selectfds); + FD_SET(0, &selectfds); + + pthread_testcancel(); + int result = select(1, &selectfds, NULL, NULL, NULL); + pthread_testcancel(); // Workaround for Linux threads suckage. + // See pthread_cancel(3). - if(wch == KEY_MOUSE) + if(result != 1) { - MEVENT ev; - getmouse(&ev); - - vscreen_post_event(new mouse_input_event(ev)); + if(errno != EINTR) + // Probably means that there was an error reading + // standard input. (could also be ENOMEM) + vscreen_post_event(new fatal_input_error(errno)); + break; } else - vscreen_post_event(new key_input_event(k)); + { + vscreen_post_event(new get_input_event(input_event_mutex, + input_event_fired, + input_event_condition)); + + while(!input_event_fired) + input_event_condition.wait(l); + input_event_fired = false; + } } } }; @@ -497,23 +592,6 @@ // The global instance; this is a Singleton. static timeout_thread instance; - // Unfortunately, technical considerations in the threading code - // mean that the actual thread object is expected to be copyable. - // Hence this proxy: - class timeout_proxy - { - timeout_thread &real_thread; - public: - timeout_proxy(timeout_thread &_real_thread) - : real_thread(_real_thread) - { - } - - void operator()() const - { - real_thread(); - } - }; public: static timeout_thread &get_instance() { @@ -531,7 +609,8 @@ throw SingletonViolationException(); } - instance.running_thread.put(new threads::thread(timeout_proxy(instance))); + threads::thread *t = new threads::thread(threads::make_bootstrap_proxy(&instance)); + instance.running_thread.put(t); } static void stop() @@ -689,6 +768,7 @@ curses_avail=true; cbreak(); + rootwin.nodelay(true); rootwin.keypad(true); global_bindings.set("Quit", quitkey);