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);