Skip to content

Commit 6398e70

Browse files
authored
Fix race condition in search command (#335)
* Add a test for search race condition When window disappear while search is trying to access it (either check its properties, or enumerate its children), X server will generate BadWindow error. Try to trigger this race, by opening 100 short living windows and execute xdotool search at the same time. The search should not crash, and since the window subject to search is there all the time, it should be found. The test relies on a specific siming, specifically that launching 100 instance of xterm takes more than 100ms, and that going from setup to the actual test takes less than 100ms. Those conditions are rather reasonable, and the test works reliably for me (at this stage - it fails - the fix is comming in the next commit). Related to #60 * Fix race condition in search command When a window is destroyed while search command tries to inspect it (get its title, class or so), the X server generates BadWindow error. The default error handler terminates the program. This means the search can fail if any window is destroyed in the meantime, regardless if the window matches the search criteria or not. Fix this by setting an alternative error handler for the search time, that ignores BadWindow error. This makes the relevant libX11 functions return (with a negative status) in case of BadWindow error, instead of interrupting the whole xdotool process. While at it, fix uninitialized 'children' variable in XQueryTree error handling. Fix #60
1 parent 14cdcf6 commit 6398e70

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

t/test_search.rb

+23
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,26 @@ def test_search_can_find_all_windows
153153
end # ["name" ... ].each
154154
end # def test_search_can_find_all_windows
155155
end # XdotoolSearchTests
156+
157+
class XdotoolRaceSearchTests < MiniTest::Test
158+
include XdoTestHelper
159+
160+
def setup
161+
setup_vars
162+
setup_ensure_x_is_healthy
163+
setup_launch_xterm
164+
setup_launch("sh", "-c", "for i in $(seq 100); do xterm -e sleep 0.1 & true; done")
165+
end # def setup
166+
167+
def test_search_title
168+
(1 .. 100).each do
169+
status, lines = xdotool "search --name #{@title}"
170+
assert_equal(0, status, "Exit status should have been 0")
171+
assert_equal(1, lines.size,
172+
"Expect only one match to our search (only one window " \
173+
"running that should match)")
174+
assert_equal(@wid, lines[0].to_i,
175+
"Expected the correct windowid when searching for its pid")
176+
end
177+
end
178+
end # XdotoolRaceSearchTests

xdo_search.c

+27-1
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,27 @@ static int check_window_match(const xdo_t *xdo, Window wid,
364364
return False;
365365
} /* int check_window_match */
366366

367+
static int ignore_badwindow(Display *dpy, XErrorEvent *xerr) {
368+
#define ERROR_BUF_SIZE 256
369+
char buf[ERROR_BUF_SIZE];
370+
char request[ERROR_BUF_SIZE];
371+
372+
if (xerr->error_code == BadWindow)
373+
return 0;
374+
375+
XGetErrorText(dpy, xerr->error_code, buf, sizeof(buf));
376+
fprintf(stderr, "X Error of failed request: %s\n", buf);
377+
378+
/* Intentionally ignore errors from protocol extensions, they are not used in
379+
* this file. */
380+
snprintf(request, sizeof(request), "%d", xerr->request_code);
381+
XGetErrorDatabaseText(dpy, "XRequest", request, "", buf,
382+
sizeof(buf));
383+
fprintf(stderr, "Major opcode: %d (%s)\n",
384+
xerr->request_code, buf);
385+
exit(1);
386+
}
387+
367388
static void find_matching_windows(const xdo_t *xdo, Window window,
368389
const xdo_search_t *search,
369390
Window **windowlist_ret,
@@ -379,8 +400,9 @@ static void find_matching_windows(const xdo_t *xdo, Window window,
379400
*/
380401

381402
Window dummy;
382-
Window *children;
403+
Window *children = NULL;
383404
unsigned int i, nchildren;
405+
int (*old_error_handler)(Display *dpy, XErrorEvent *xerr);
384406

385407
/* Break early, if we have enough windows already. */
386408
if (search->limit > 0 && *nwindows_ret >= search->limit) {
@@ -392,6 +414,9 @@ static void find_matching_windows(const xdo_t *xdo, Window window,
392414
return;
393415
}
394416

417+
/* Don't crash if window dissappear in the meantime */
418+
old_error_handler = XSetErrorHandler(ignore_badwindow);
419+
395420
/* Break if XQueryTree fails.
396421
* TODO(sissel): report an error? */
397422
Status success = XQueryTree(xdo->xdpy, window, &dummy, &dummy, &children, &nchildren);
@@ -422,6 +447,7 @@ static void find_matching_windows(const xdo_t *xdo, Window window,
422447
*windowlist_size * sizeof(Window));
423448
}
424449
} /* for (i in children) ... */
450+
XSetErrorHandler(old_error_handler);
425451

426452
/* Now check children-children */
427453
if (search->max_depth == -1 || (current_depth + 1) <= search->max_depth) {

0 commit comments

Comments
 (0)