-
Notifications
You must be signed in to change notification settings - Fork 190
Description
Background
The SWT codebase contains a comment from 2004 in Display.sleep() (GTK implementation) claiming there's a bug in GTK:
/*
* Bug in GTK. For some reason, g_main_context_wakeup() may
* fail to wake up the UI thread from the polling function.
* The fix is to sleep for a maximum of 50 milliseconds.
*/
if (timeout [0] < 0) timeout [0] = 50;Source: Commit 400a41972553b6a4188a913a649f045d7101753f from October 8, 2004 by Silenio Quarti.
This document presents evidence that this comment is outdated and g_main_context_wakeup works correctly on modern GLib.
Web Research for Bug Reports
The following sources were searched for known issues with g_main_context_wakeup:
| Source | Search Query | Result |
|---|---|---|
| GNOME GitLab Issues | g_main_context_wakeup |
No relevant issues found |
| Eclipse Bugzilla | g_main_context_wakeup |
No bugs found |
| Eclipse Bugzilla | Display sleep wake GTK |
No directly related bugs |
| GLib Documentation | g_main_context_wakeup |
Documents expected behavior, no known issues mentioned |
| Stack Overflow | g_main_context_wakeup not working |
Blocked by CAPTCHA, but general searches show no widespread issues |
GLib Official Documentation (https://docs.gtk.org/glib/method.MainContext.wakeup.html):
Wake up
contextif it's currently blocking ing_main_context_iteration(), causing it to stop blocking.The
contextcould be blocking waiting for a source to become ready. Otherwise, ifcontextis not currently blocking, this function causes the next invocation ofg_main_context_iteration()to return without blocking.
The documentation describes the expected behavior and does not mention any reliability issues.
Test Environment
- GLib Version: 2.84.4 (libglib2.0-dev)
- Operating System: Linux (Debian-based)
- Architecture: x86_64
Test Programs
All tests were compiled with:
gcc -o test_wakeup test_wakeup.c $(pkg-config --cflags --libs glib-2.0) -pthreadTest 1: Basic Wakeup During Active Poll
Purpose: Verify that g_main_context_wakeup can wake a thread that is currently polling.
#include <glib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
static volatile int stop = 0;
static GMainContext *ctx = NULL;
void* waker_thread(void* arg) {
sleep(1); // Wait for main to start polling
printf("Waker: calling g_main_context_wakeup\n");
g_main_context_wakeup(ctx);
printf("Waker: wakeup called\n");
return NULL;
}
int main() {
ctx = g_main_context_default();
pthread_t thread;
pthread_create(&thread, NULL, waker_thread, NULL);
// Replicate SWT's polling pattern
int max_priority, timeout;
GPollFD fds[10];
int nfds;
if (g_main_context_acquire(ctx)) {
g_main_context_prepare(ctx, &max_priority);
nfds = g_main_context_query(ctx, max_priority, &timeout, fds, 10);
printf("Main: nfds=%d, timeout=%d\n", nfds, timeout);
printf("Main: about to poll for 5 seconds...\n");
// Simulate SWT's poll with long timeout
timeout = 5000; // 5 seconds
int result = g_poll(fds, nfds, timeout);
printf("Main: poll returned %d\n", result);
g_main_context_check(ctx, max_priority, fds, nfds);
g_main_context_release(ctx);
}
pthread_join(thread, NULL);
return 0;
}Result:
Main: nfds=1, timeout=-1
Main: about to poll for 5 seconds...
Waker: calling g_main_context_wakeup
Waker: wakeup called
Main: poll returned 1
✅ PASS - Poll returned immediately after wakeup (1 second), not after 5 seconds.
Test 2: Wakeup Before Poll Starts
Purpose: Verify that g_main_context_wakeup called before poll() starts will still cause the poll to return immediately.
#include <glib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
static GMainContext *ctx = NULL;
void* waker_thread(void* arg) {
// Call wakeup BEFORE poll starts
usleep(100000); // 100ms - after prepare, before poll
printf("Waker: calling g_main_context_wakeup (before poll starts)\n");
g_main_context_wakeup(ctx);
printf("Waker: wakeup called\n");
return NULL;
}
int main() {
ctx = g_main_context_default();
pthread_t thread;
pthread_create(&thread, NULL, waker_thread, NULL);
int max_priority, timeout;
GPollFD fds[10];
int nfds;
if (g_main_context_acquire(ctx)) {
g_main_context_prepare(ctx, &max_priority);
nfds = g_main_context_query(ctx, max_priority, &timeout, fds, 10);
printf("Main: nfds=%d, timeout=%d\n", nfds, timeout);
// Simulate some delay between query and poll (like SWT does with wake=false)
usleep(200000); // 200ms - wakeup happens during this time
printf("Main: about to poll for 5 seconds...\n");
timeout = 5000;
int result = g_poll(fds, nfds, timeout);
printf("Main: poll returned %d\n", result);
g_main_context_check(ctx, max_priority, fds, nfds);
g_main_context_release(ctx);
}
pthread_join(thread, NULL);
return 0;
}Result:
Main: nfds=1, timeout=-1
Waker: calling g_main_context_wakeup (before poll starts)
Waker: wakeup called
Main: about to poll for 5 seconds...
Main: poll returned 1
✅ PASS - The wakeup fd remains signaled until consumed, so poll returns immediately even though wakeup was called before poll started.
Test 3: Wakeup Via Function Pointer (SWT Pattern)
Purpose: Verify that calling poll via g_main_context_get_poll_func() (as SWT does) doesn't affect wakeup behavior.
#include <glib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
static GMainContext *ctx = NULL;
void* waker_thread(void* arg) {
usleep(500000); // 500ms
printf("Waker: calling g_main_context_wakeup\n");
g_main_context_wakeup(ctx);
printf("Waker: wakeup called\n");
return NULL;
}
int main() {
ctx = g_main_context_default();
pthread_t thread;
pthread_create(&thread, NULL, waker_thread, NULL);
int max_priority, timeout;
GPollFD fds[10];
int nfds;
if (g_main_context_acquire(ctx)) {
g_main_context_prepare(ctx, &max_priority);
nfds = g_main_context_query(ctx, max_priority, &timeout, fds, 10);
// Get the poll function like SWT does
GPollFunc poll_func = g_main_context_get_poll_func(ctx);
printf("Main: nfds=%d, timeout=%d, poll_func=%p\n", nfds, timeout, (void*)poll_func);
printf("Main: about to call poll_func for 5 seconds...\n");
// Call poll via function pointer like SWT does
timeout = 5000;
int result = poll_func(fds, nfds, timeout);
printf("Main: poll_func returned %d\n", result);
g_main_context_check(ctx, max_priority, fds, nfds);
g_main_context_release(ctx);
}
pthread_join(thread, NULL);
return 0;
}Result:
Main: nfds=1, timeout=-1, poll_func=0x7f59db488750
Main: about to call poll_func for 5 seconds...
Waker: calling g_main_context_wakeup
Waker: wakeup called
Main: poll_func returned 1
✅ PASS - Using the poll function pointer (as SWT does) works correctly.
Test 4: Wakeup With NULL Context
Purpose: Verify that g_main_context_wakeup(NULL) (which SWT uses via OS.g_main_context_wakeup(0)) works correctly.
#include <glib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
static GMainContext *ctx = NULL;
void* waker_thread(void* arg) {
usleep(100000); // 100ms
printf("Waker: calling g_main_context_wakeup with NULL (default context)\n");
// SWT calls with 0 (NULL) which means default context
g_main_context_wakeup(NULL);
printf("Waker: done\n");
return NULL;
}
int main() {
// Get default context explicitly
ctx = g_main_context_default();
pthread_t thread;
pthread_create(&thread, NULL, waker_thread, NULL);
int max_priority, timeout;
GPollFD fds[10];
int nfds;
if (g_main_context_acquire(ctx)) {
g_main_context_prepare(ctx, &max_priority);
nfds = g_main_context_query(ctx, max_priority, &timeout, fds, 10);
GPollFunc poll_func = g_main_context_get_poll_func(ctx);
if (timeout < 0) timeout = 5000; // 5 second timeout
printf("Main: nfds=%d, polling for %dms...\n", nfds, timeout);
int poll_result = poll_func(fds, nfds, timeout);
printf("Main: poll returned %d\n", poll_result);
g_main_context_check(ctx, max_priority, fds, nfds);
g_main_context_release(ctx);
}
pthread_join(thread, NULL);
return 0;
}Result:
Main: nfds=1, polling for 5000ms...
Waker: calling g_main_context_wakeup with NULL (default context)
Waker: done
Main: poll returned 1
✅ PASS - Using NULL (default context) works correctly.
Test 5: SWT's Exact Loop Pattern with Race Conditions
Purpose: Simulate SWT's Display.sleep() loop pattern including the wake flag reset.
#include <glib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdbool.h>
#include <time.h>
static GMainContext *ctx = NULL;
static volatile bool wake = false;
static volatile bool wakeup_called = false;
void* waker_thread(void* arg) {
usleep(100000); // 100ms - let main start its loop
printf("Waker: calling g_main_context_wakeup\n");
g_main_context_wakeup(ctx);
wake = true;
wakeup_called = true;
printf("Waker: done\n");
return NULL;
}
int main() {
ctx = g_main_context_default();
pthread_t thread;
pthread_create(&thread, NULL, waker_thread, NULL);
int max_priority, timeout;
GPollFD fds[10];
int nfds;
bool result = false;
int iteration = 0;
struct timespec start, now;
clock_gettime(CLOCK_MONOTONIC, &start);
// Simulate SWT's sleep loop
do {
iteration++;
if (g_main_context_acquire(ctx)) {
result = g_main_context_prepare(ctx, &max_priority);
nfds = g_main_context_query(ctx, max_priority, &timeout, fds, 10);
GPollFunc poll_func = g_main_context_get_poll_func(ctx);
if (nfds > 0 || timeout != 0) {
if (timeout < 0) timeout = 50; // SWT's 50ms cap
wake = false; // Reset flag BEFORE poll (like SWT)
int poll_result = poll_func(fds, nfds, timeout);
clock_gettime(CLOCK_MONOTONIC, &now);
long elapsed_ms = (now.tv_sec - start.tv_sec) * 1000 +
(now.tv_nsec - start.tv_nsec) / 1000000;
printf("Iter %d @ %ldms: poll=%d, wake=%d, wakeup_called=%d\n",
iteration, elapsed_ms, poll_result, wake, wakeup_called);
}
g_main_context_check(ctx, max_priority, fds, nfds);
g_main_context_release(ctx);
}
} while (!result && !wake && iteration < 20);
clock_gettime(CLOCK_MONOTONIC, &now);
long elapsed_ms = (now.tv_sec - start.tv_sec) * 1000 +
(now.tv_nsec - start.tv_nsec) / 1000000;
printf("Loop exited at iteration %d, elapsed %ldms, wake=%d\n",
iteration, elapsed_ms, wake);
pthread_join(thread, NULL);
return 0;
}Result:
Iter 1 @ 50ms: poll=0, wake=0, wakeup_called=0
Waker: calling g_main_context_wakeup
Waker: done
Iter 2 @ 100ms: poll=1, wake=1, wakeup_called=1
Loop exited at iteration 2, elapsed 100ms, wake=1
✅ PASS - Even with SWT's exact pattern (including wake = false before poll), the wakeup works correctly.
Conclusion
All five tests demonstrate that g_main_context_wakeup works correctly on modern GLib (2.84):
| Test | Scenario | Result |
|---|---|---|
| 1 | Basic wakeup during poll | ✅ PASS |
| 2 | Wakeup before poll starts | ✅ PASS |
| 3 | Wakeup via function pointer | ✅ PASS |
| 4 | Wakeup with NULL context | ✅ PASS |
| 5 | SWT's exact loop pattern | ✅ PASS |
Recommendation
Remove or update the outdated comment in Display.sleep():
// BEFORE (inaccurate):
/*
* Bug in GTK. For some reason, g_main_context_wakeup() may
* fail to wake up the UI thread from the polling function.
* The fix is to sleep for a maximum of 50 milliseconds.
*/
if (timeout [0] < 0) timeout [0] = 50;
// AFTER (accurate):
/*
* Cap the poll timeout to ensure the event loop remains responsive
* even if wakeup signals are missed due to race conditions in SWT's
* wake flag handling. This is not a GTK bug but a defensive measure.
*/
if (timeout [0] < 0) timeout [0] = 50;Alternatively, investigate whether the 50ms cap is still necessary at all, as g_main_context_wakeup reliably wakes the poll via the wakeup eventfd.
Historical Context
The original comment was added in October 2004 when:
- GLib version was likely 2.4.x or 2.6.x
- The wakeup mechanism may have been implemented differently
- There may have been platform-specific issues that have since been fixed
Modern GLib (2.56+) uses a reliable eventfd-based wakeup mechanism that is well-tested and widely used.