st/wgl: Install our windows message hook to threads created before the ICD is loaded.
authorJosé Fonseca <jfonseca@vmware.com>
Thu, 4 Apr 2013 19:27:39 +0000 (20:27 +0100)
committerJosé Fonseca <jfonseca@vmware.com>
Fri, 5 Apr 2013 17:27:54 +0000 (18:27 +0100)
Otherwise we will not receive destroy windows events, causing framebuffers
to leak.

This happens particularly with java and jogl.

Tested with java + jogl, MATLAB.

VMware Internal Bug Number: 1013086.

Reviewed-by: Brian Paul <brianp@vmware.com>
src/gallium/state_trackers/wgl/stw_tls.c
src/gallium/state_trackers/wgl/stw_tls.h

index 4bd6a9289c96fa0d9a1a362d4e360dea5bb77acb..c0da930b214aca8fefa0b097d1e1c6d20c84472b 100644 (file)
@@ -1,6 +1,6 @@
 /**************************************************************************
  *
- * Copyright 2009 VMware, Inc.
+ * Copyright 2009-2013 VMware, Inc.
  * All Rights Reserved.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  **************************************************************************/
 
 #include <windows.h>
+#include <tlhelp32.h>
 
 #include "pipe/p_compiler.h"
-#include "util/u_memory.h"
+#include "util/u_debug.h"
 #include "stw_tls.h"
 
 static DWORD tlsIndex = TLS_OUT_OF_INDEXES;
 
+
+/**
+ * Static mutex to protect the access to g_pendingTlsData global and
+ * stw_tls_data::next member.
+ */
+static CRITICAL_SECTION g_mutex = {
+   (PCRITICAL_SECTION_DEBUG)-1, -1, 0, 0, 0, 0
+};
+
+/**
+ * There is no way to invoke TlsSetValue for a different thread, so we
+ * temporarily put the thread data for non-current threads here.
+ */
+static struct stw_tls_data *g_pendingTlsData = NULL;
+
+
+static INLINE struct stw_tls_data *
+stw_tls_data_create(DWORD dwThreadId);
+
+static struct stw_tls_data *
+stw_tls_lookup_pending_data(DWORD dwThreadId);
+
+
 boolean
 stw_tls_init(void)
 {
@@ -41,35 +65,110 @@ stw_tls_init(void)
       return FALSE;
    }
 
+   /*
+    * DllMain is called with DLL_THREAD_ATTACH only for threads created after
+    * the DLL is loaded by the process.  So enumerate and add our hook to all
+    * previously existing threads.
+    *
+    * XXX: Except for the current thread since it there is an explicit
+    * stw_tls_init_thread() call for it later on.
+    */
+   if (1) {
+      DWORD dwCurrentProcessId = GetCurrentProcessId();
+      DWORD dwCurrentThreadId = GetCurrentThreadId();
+      HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwCurrentProcessId);
+      if (hSnapshot != INVALID_HANDLE_VALUE) {
+         THREADENTRY32 te;
+         te.dwSize = sizeof te;
+         if (Thread32First(hSnapshot, &te)) {
+            do {
+               if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) +
+                                sizeof te.th32OwnerProcessID) {
+                  if (te.th32OwnerProcessID == dwCurrentProcessId) {
+                     if (te.th32ThreadID != dwCurrentThreadId) {
+                        struct stw_tls_data *data;
+                        data = stw_tls_data_create(te.th32ThreadID);
+                        if (data) {
+                           EnterCriticalSection(&g_mutex);
+                           data->next = g_pendingTlsData;
+                           g_pendingTlsData = data;
+                           LeaveCriticalSection(&g_mutex);
+                        }
+                     }
+                  }
+               }
+               te.dwSize = sizeof te;
+            } while (Thread32Next(hSnapshot, &te));
+         }
+         CloseHandle(hSnapshot);
+      }
+   }
+
    return TRUE;
 }
 
+
+/**
+ * Install windows hook for a given thread (not necessarily the current one).
+ */
 static INLINE struct stw_tls_data *
-stw_tls_data_create()
+stw_tls_data_create(DWORD dwThreadId)
 {
    struct stw_tls_data *data;
 
-   data = CALLOC_STRUCT(stw_tls_data);
-   if (!data)
+   if (0) {
+      debug_printf("%s(0x%04lx)\n", __FUNCTION__, dwThreadId);
+   }
+
+   data = (struct stw_tls_data *)calloc(1, sizeof *data);
+   if (!data) {
       goto no_data;
+   }
+
+   data->dwThreadId = dwThreadId;
 
    data->hCallWndProcHook = SetWindowsHookEx(WH_CALLWNDPROC,
                                              stw_call_window_proc,
                                              NULL,
-                                             GetCurrentThreadId());
-   if(data->hCallWndProcHook == NULL)
+                                             dwThreadId);
+   if (data->hCallWndProcHook == NULL) {
       goto no_hook;
-
-   TlsSetValue(tlsIndex, data);
+   }
 
    return data;
 
 no_hook:
-   FREE(data);
+   free(data);
 no_data:
    return NULL;
 }
 
+/**
+ * Destroy the per-thread data/hook.
+ *
+ * It is important to remove all hooks when unloading our DLL, otherwise our
+ * hook function might be called after it is no longer there.
+ */
+static void
+stw_tls_data_destroy(struct stw_tls_data *data)
+{
+   assert(data);
+   if (!data) {
+      return;
+   }
+
+   if (0) {
+      debug_printf("%s(0x%04lx)\n", __FUNCTION__, data->dwThreadId);
+   }
+
+   if (data->hCallWndProcHook) {
+      UnhookWindowsHookEx(data->hCallWndProcHook);
+      data->hCallWndProcHook = NULL;
+   }
+
+   free(data);
+}
+
 boolean
 stw_tls_init_thread(void)
 {
@@ -79,9 +178,12 @@ stw_tls_init_thread(void)
       return FALSE;
    }
 
-   data = stw_tls_data_create();
-   if(!data)
+   data = stw_tls_data_create(GetCurrentThreadId());
+   if (!data) {
       return FALSE;
+   }
+
+   TlsSetValue(tlsIndex, data);
 
    return TRUE;
 }
@@ -96,15 +198,15 @@ stw_tls_cleanup_thread(void)
    }
 
    data = (struct stw_tls_data *) TlsGetValue(tlsIndex);
-   if(data) {
+   if (data) {
       TlsSetValue(tlsIndex, NULL);
-   
-      if(data->hCallWndProcHook) {
-         UnhookWindowsHookEx(data->hCallWndProcHook);
-         data->hCallWndProcHook = NULL;
-      }
-   
-      FREE(data);
+   } else {
+      /* See if there this thread's data in on the pending list */
+      data = stw_tls_lookup_pending_data(GetCurrentThreadId());
+   }
+
+   if (data) {
+      stw_tls_data_destroy(data);
    }
 }
 
@@ -112,11 +214,52 @@ void
 stw_tls_cleanup(void)
 {
    if (tlsIndex != TLS_OUT_OF_INDEXES) {
+      /*
+       * Destroy all items in g_pendingTlsData linked list.
+       */
+      EnterCriticalSection(&g_mutex);
+      while (g_pendingTlsData) {
+         struct stw_tls_data * data = g_pendingTlsData;
+         g_pendingTlsData = data->next;
+         stw_tls_data_destroy(data);
+      }
+      LeaveCriticalSection(&g_mutex);
+
       TlsFree(tlsIndex);
       tlsIndex = TLS_OUT_OF_INDEXES;
    }
 }
 
+/*
+ * Search for the current thread in the g_pendingTlsData linked list.
+ *
+ * It will remove and return the node on success, or return NULL on failure.
+ */
+static struct stw_tls_data *
+stw_tls_lookup_pending_data(DWORD dwThreadId)
+{
+   struct stw_tls_data ** p_data;
+   struct stw_tls_data *data = NULL;
+
+   EnterCriticalSection(&g_mutex);
+   for (p_data = &g_pendingTlsData; *p_data; p_data = &(*p_data)->next) {
+      if ((*p_data)->dwThreadId == dwThreadId) {
+         data = *p_data;
+        
+        /*
+         * Unlink the node.
+         */
+         *p_data = data->next;
+         data->next = NULL;
+         
+        break;
+      }
+   }
+   LeaveCriticalSection(&g_mutex);
+
+   return data;
+}
+
 struct stw_tls_data *
 stw_tls_get_data(void)
 {
@@ -127,13 +270,36 @@ stw_tls_get_data(void)
    }
    
    data = (struct stw_tls_data *) TlsGetValue(tlsIndex);
-   if(!data) {
-      /* DllMain is called with DLL_THREAD_ATTACH only by threads created after 
-       * the DLL is loaded by the process */
-      data = stw_tls_data_create();
-      if(!data)
-         return NULL;
+   if (!data) {
+      DWORD dwCurrentThreadId = GetCurrentThreadId();
+
+      /*
+       * Search for the current thread in the g_pendingTlsData linked list.
+       */
+      data = stw_tls_lookup_pending_data(dwCurrentThreadId);
+
+      if (!data) {
+         /*
+          * This should be impossible now.
+          */
+        assert(!"Failed to find thread data for thread id");
+
+         /*
+          * DllMain is called with DLL_THREAD_ATTACH only by threads created
+          * after the DLL is loaded by the process
+          */
+         data = stw_tls_data_create(dwCurrentThreadId);
+         if (!data) {
+            return NULL;
+         }
+      }
+
+      TlsSetValue(tlsIndex, data);
    }
 
+   assert(data);
+   assert(data->dwThreadId = GetCurrentThreadId());
+   assert(data->next == NULL);
+
    return data;
 }
index fbf8b1cbee495461cfe4456eaaa11af0985f60bc..0d2cf6b06e86bde3bf9ee71a050184ae6fcc7f40 100644 (file)
 
 struct stw_tls_data
 {
+   DWORD dwThreadId;
+
    HHOOK hCallWndProcHook;
+
+   struct stw_tls_data *next;
 };
 
 boolean