Skip to content

g_main_context_wakeup Is Maybe Not Buggy #3053

@laeubi

Description

@laeubi

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 context if it's currently blocking in g_main_context_iteration(), causing it to stop blocking.

The context could be blocking waiting for a source to become ready. Otherwise, if context is not currently blocking, this function causes the next invocation of g_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) -pthread

Test 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions