gthread-win32.c 17.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
/* GLIB - Library of useful routines for C programming
 * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * gthread.c: solaris thread system implementation
 * Copyright 1998-2001 Sebastian Wilhelmi; University of Karlsruhe
 * Copyright 2001 Hans Breuer
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
13
14
15
16
17
18
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
20
21
22
23
24
25
 */

/*
 * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
 * file for a list of people on the GLib Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
26
 * GLib at ftp://ftp.gtk.org/pub/gtk/.
27
28
 */

29
30
31
32
33
34
35
36
37
38
/* The GMutex and GCond implementations in this file are some of the
 * lowest-level code in GLib.  All other parts of GLib (messages,
 * memory, slices, etc) assume that they can freely use these facilities
 * without risking recursion.
 *
 * As such, these functions are NOT permitted to call any other part of
 * GLib.
 *
 * The thread manipulation functions (create, exit, join, etc.) have
 * more freedom -- they can do as they please.
39
40
 */

41
#include "config.h"
42

Matthias Clasen's avatar
Matthias Clasen committed
43
#include "glib.h"
Dan Winship's avatar
Dan Winship committed
44
#include "glib-init.h"
45
#include "gthread.h"
Allison Karlitskaya's avatar
Allison Karlitskaya committed
46
#include "gthreadprivate.h"
47
#include "gslice.h"
48
49
50
51

#include <windows.h>

#include <process.h>
52
#include <stdlib.h>
53
#include <stdio.h>
54

55
56
57
58
59
60
static void
g_thread_abort (gint         status,
                const gchar *function)
{
  fprintf (stderr, "GLib (gthread-win32.c): Unexpected error from C library during '%s': %s.  Aborting.\n",
           strerror (status), function);
61
  g_abort ();
62
}
63

64
/* Starting with Vista and Windows 2008, we have access to the
65
66
 * CONDITION_VARIABLE and SRWLock primitives on Windows, which are
 * pretty reasonable approximations of the primitives specified in
67
68
69
70
71
72
73
 * POSIX 2001 (pthread_cond_t and pthread_mutex_t respectively).
 *
 * Both of these types are structs containing a single pointer.  That
 * pointer is used as an atomic bitfield to support user-space mutexes
 * that only get the kernel involved in cases of contention (similar
 * to how futex()-based mutexes work on Linux).  The biggest advantage
 * of these new types is that they can be statically initialised to
Allison Karlitskaya's avatar
Allison Karlitskaya committed
74
75
 * zero.  That means that they are completely ABI compatible with our
 * GMutex and GCond APIs.
76
77
78
79
80
81
 */

/* {{{1 GMutex */
void
g_mutex_init (GMutex *mutex)
{
82
  InitializeSRWLock ((gpointer) mutex);
83
}
84

85
86
void
g_mutex_clear (GMutex *mutex)
87
{
88
}
89

90
91
void
g_mutex_lock (GMutex *mutex)
92
{
93
  AcquireSRWLockExclusive ((gpointer) mutex);
94
95
}

96
97
gboolean
g_mutex_trylock (GMutex *mutex)
98
{
99
  return TryAcquireSRWLockExclusive ((gpointer) mutex);
100
101
}

102
103
void
g_mutex_unlock (GMutex *mutex)
104
{
105
  ReleaseSRWLockExclusive ((gpointer) mutex);
106
107
}

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* {{{1 GRecMutex */

static CRITICAL_SECTION *
g_rec_mutex_impl_new (void)
{
  CRITICAL_SECTION *cs;

  cs = g_slice_new (CRITICAL_SECTION);
  InitializeCriticalSection (cs);

  return cs;
}

static void
g_rec_mutex_impl_free (CRITICAL_SECTION *cs)
{
  DeleteCriticalSection (cs);
  g_slice_free (CRITICAL_SECTION, cs);
}

static CRITICAL_SECTION *
g_rec_mutex_get_impl (GRecMutex *mutex)
{
131
  CRITICAL_SECTION *impl = mutex->p;
132

133
  if G_UNLIKELY (mutex->p == NULL)
134
135
    {
      impl = g_rec_mutex_impl_new ();
136
      if (InterlockedCompareExchangePointer (&mutex->p, impl, NULL) != NULL)
137
        g_rec_mutex_impl_free (impl);
138
      impl = mutex->p;
139
140
141
142
143
144
145
146
    }

  return impl;
}

void
g_rec_mutex_init (GRecMutex *mutex)
{
147
  mutex->p = g_rec_mutex_impl_new ();
148
149
150
151
152
}

void
g_rec_mutex_clear (GRecMutex *mutex)
{
153
  g_rec_mutex_impl_free (mutex->p);
154
155
156
157
158
159
160
161
162
163
164
}

void
g_rec_mutex_lock (GRecMutex *mutex)
{
  EnterCriticalSection (g_rec_mutex_get_impl (mutex));
}

void
g_rec_mutex_unlock (GRecMutex *mutex)
{
165
  LeaveCriticalSection (mutex->p);
166
167
168
169
170
171
172
173
}

gboolean
g_rec_mutex_trylock (GRecMutex *mutex)
{
  return TryEnterCriticalSection (g_rec_mutex_get_impl (mutex));
}

Allison Karlitskaya's avatar
Allison Karlitskaya committed
174
175
176
177
178
/* {{{1 GRWLock */

void
g_rw_lock_init (GRWLock *lock)
{
179
  InitializeSRWLock ((gpointer) lock);
Allison Karlitskaya's avatar
Allison Karlitskaya committed
180
181
182
183
184
185
186
187
188
189
}

void
g_rw_lock_clear (GRWLock *lock)
{
}

void
g_rw_lock_writer_lock (GRWLock *lock)
{
190
  AcquireSRWLockExclusive ((gpointer) lock);
Allison Karlitskaya's avatar
Allison Karlitskaya committed
191
192
193
194
195
}

gboolean
g_rw_lock_writer_trylock (GRWLock *lock)
{
196
  return TryAcquireSRWLockExclusive ((gpointer) lock);
Allison Karlitskaya's avatar
Allison Karlitskaya committed
197
198
199
200
201
}

void
g_rw_lock_writer_unlock (GRWLock *lock)
{
202
  ReleaseSRWLockExclusive ((gpointer) lock);
Allison Karlitskaya's avatar
Allison Karlitskaya committed
203
204
205
206
207
}

void
g_rw_lock_reader_lock (GRWLock *lock)
{
208
  AcquireSRWLockShared ((gpointer) lock);
Allison Karlitskaya's avatar
Allison Karlitskaya committed
209
210
211
212
213
}

gboolean
g_rw_lock_reader_trylock (GRWLock *lock)
{
214
  return TryAcquireSRWLockShared ((gpointer) lock);
Allison Karlitskaya's avatar
Allison Karlitskaya committed
215
216
217
218
219
}

void
g_rw_lock_reader_unlock (GRWLock *lock)
{
220
  ReleaseSRWLockShared ((gpointer) lock);
Allison Karlitskaya's avatar
Allison Karlitskaya committed
221
222
}

223
224
225
/* {{{1 GCond */
void
g_cond_init (GCond *cond)
226
{
227
  InitializeConditionVariable ((gpointer) cond);
228
229
}

230
231
void
g_cond_clear (GCond *cond)
232
233
234
{
}

235
236
void
g_cond_signal (GCond *cond)
237
{
238
  WakeConditionVariable ((gpointer) cond);
239
240
}

241
242
void
g_cond_broadcast (GCond *cond)
243
{
244
  WakeAllConditionVariable ((gpointer) cond);
245
246
}

247
248
249
void
g_cond_wait (GCond  *cond,
             GMutex *entered_mutex)
250
{
251
  SleepConditionVariableSRW ((gpointer) cond, (gpointer) entered_mutex, INFINITE, 0);
252
253
}

254
gboolean
255
256
257
g_cond_wait_until (GCond  *cond,
                   GMutex *entered_mutex,
                   gint64  end_time)
258
{
259
260
261
  gint64 span, start_time;
  DWORD span_millis;
  gboolean signalled;
262

263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
  start_time = g_get_monotonic_time ();
  do
    {
      span = end_time - start_time;

      if G_UNLIKELY (span < 0)
        span_millis = 0;
      else if G_UNLIKELY (span > G_GINT64_CONSTANT (1000) * (DWORD) INFINITE)
        span_millis = INFINITE;
      else
        /* Round up so we don't time out too early */
        span_millis = (span + 1000 - 1) / 1000;

      /* We never want to wait infinitely */
      if (span_millis >= INFINITE)
        span_millis = INFINITE - 1;

280
      signalled = SleepConditionVariableSRW ((gpointer) cond, (gpointer) entered_mutex, span_millis, 0);
281
282
283
284
285
286
287
288
      if (signalled)
        break;

      /* In case we didn't wait long enough after a timeout, wait again for the
       * remaining time */
      start_time = g_get_monotonic_time ();
    }
  while (start_time < end_time);
289

290
  return signalled;
291
}
292

293
/* {{{1 GPrivate */
294

Allison Karlitskaya's avatar
Allison Karlitskaya committed
295
296
297
298
299
300
301
302
303
typedef struct _GPrivateDestructor GPrivateDestructor;

struct _GPrivateDestructor
{
  DWORD               index;
  GDestroyNotify      notify;
  GPrivateDestructor *next;
};

304
static GPrivateDestructor *g_private_destructors;  /* (atomic) prepend-only */
305
static CRITICAL_SECTION g_private_lock;
Allison Karlitskaya's avatar
Allison Karlitskaya committed
306

307
308
static DWORD
g_private_get_impl (GPrivate *key)
Allison Karlitskaya's avatar
Allison Karlitskaya committed
309
{
310
  DWORD impl = (DWORD) GPOINTER_TO_UINT(key->p);
Allison Karlitskaya's avatar
Allison Karlitskaya committed
311

312
313
314
  if G_UNLIKELY (impl == 0)
    {
      EnterCriticalSection (&g_private_lock);
315
      impl = (UINT_PTR) key->p;
316
317
318
      if (impl == 0)
        {
          GPrivateDestructor *destructor;
Allison Karlitskaya's avatar
Allison Karlitskaya committed
319

320
          impl = TlsAlloc ();
Allison Karlitskaya's avatar
Allison Karlitskaya committed
321

322
323
324
325
326
327
328
329
330
331
332
          if G_UNLIKELY (impl == 0)
            {
              /* Ignore TLS index 0 temporarily (as 0 is the indicator that we
               * haven't allocated TLS yet) and alloc again;
               * See https://gitlab.gnome.org/GNOME/glib/-/issues/2058 */
              DWORD impl2 = TlsAlloc ();
              TlsFree (impl);
              impl = impl2;
            }

          if (impl == TLS_OUT_OF_INDEXES || impl == 0)
333
334
335
336
337
338
339
340
341
            g_thread_abort (0, "TlsAlloc");

          if (key->notify != NULL)
            {
              destructor = malloc (sizeof (GPrivateDestructor));
              if G_UNLIKELY (destructor == NULL)
                g_thread_abort (errno, "malloc");
              destructor->index = impl;
              destructor->notify = key->notify;
342
              destructor->next = g_atomic_pointer_get (&g_private_destructors);
343
344
345
346
347
348
349

              /* We need to do an atomic store due to the unlocked
               * access to the destructor list from the thread exit
               * function.
               *
               * It can double as a sanity check...
               */
350
351
352
              if (!g_atomic_pointer_compare_and_exchange (&g_private_destructors,
                                                          destructor->next,
                                                          destructor))
353
354
355
356
                g_thread_abort (0, "g_private_get_impl(1)");
            }

          /* Ditto, due to the unlocked access on the fast path */
357
          if (!g_atomic_pointer_compare_and_exchange (&key->p, NULL, impl))
358
359
360
361
            g_thread_abort (0, "g_private_get_impl(2)");
        }
      LeaveCriticalSection (&g_private_lock);
    }
Allison Karlitskaya's avatar
Allison Karlitskaya committed
362

363
  return impl;
Allison Karlitskaya's avatar
Allison Karlitskaya committed
364
365
366
}

gpointer
Allison Karlitskaya's avatar
Allison Karlitskaya committed
367
g_private_get (GPrivate *key)
Allison Karlitskaya's avatar
Allison Karlitskaya committed
368
{
369
  return TlsGetValue (g_private_get_impl (key));
Allison Karlitskaya's avatar
Allison Karlitskaya committed
370
371
372
}

void
Allison Karlitskaya's avatar
Allison Karlitskaya committed
373
g_private_set (GPrivate *key,
Allison Karlitskaya's avatar
Allison Karlitskaya committed
374
375
               gpointer  value)
{
376
377
378
379
380
381
382
383
384
  TlsSetValue (g_private_get_impl (key), value);
}

void
g_private_replace (GPrivate *key,
                   gpointer  value)
{
  DWORD impl = g_private_get_impl (key);
  gpointer old;
Allison Karlitskaya's avatar
Allison Karlitskaya committed
385

386
  old = TlsGetValue (impl);
387
  TlsSetValue (impl, value);
388
389
  if (old && key->notify)
    key->notify (old);
Allison Karlitskaya's avatar
Allison Karlitskaya committed
390
391
392
393
}

/* {{{1 GThread */

394
395
396
397
398
399
#define win32_check_for_error(what) G_STMT_START{			\
  if (!(what))								\
    g_error ("file %s: line %d (%s): error %s during %s",		\
	     __FILE__, __LINE__, G_STRFUNC,				\
	     g_win32_error_message (GetLastError ()), #what);		\
  }G_STMT_END
400

401
402
403
404
#define G_MUTEX_SIZE (sizeof (gpointer))

typedef BOOL (__stdcall *GTryEnterCriticalSectionFunc) (CRITICAL_SECTION *);

405
406
407
408
typedef struct
{
  GRealThread thread;

Allison Karlitskaya's avatar
Allison Karlitskaya committed
409
410
  GThreadFunc proxy;
  HANDLE      handle;
411
412
} GThreadWin32;

413
414
415
void
g_system_thread_free (GRealThread *thread)
{
416
417
  GThreadWin32 *wt = (GThreadWin32 *) thread;

Allison Karlitskaya's avatar
Allison Karlitskaya committed
418
  win32_check_for_error (CloseHandle (wt->handle));
419
  g_slice_free (GThreadWin32, wt);
420
421
}

422
423
void
g_system_thread_exit (void)
424
425
426
427
428
{
  _endthreadex (0);
}

static guint __stdcall
429
g_thread_win32_proxy (gpointer data)
430
{
Allison Karlitskaya's avatar
Allison Karlitskaya committed
431
  GThreadWin32 *self = data;
432

Allison Karlitskaya's avatar
Allison Karlitskaya committed
433
  self->proxy (self);
434

435
  g_system_thread_exit ();
436
437
438
439
440
441

  g_assert_not_reached ();

  return 0;
}

442
gboolean
443
444
445
446
g_system_thread_get_scheduler_settings (GThreadSchedulerSettings *scheduler_settings)
{
  HANDLE current_thread = GetCurrentThread ();
  scheduler_settings->thread_prio = GetThreadPriority (current_thread);
447
448

  return TRUE;
449
450
}

451
GRealThread *
452
453
454
455
456
457
458
g_system_thread_new (GThreadFunc proxy,
                     gulong stack_size,
                     const GThreadSchedulerSettings *scheduler_settings,
                     const char *name,
                     GThreadFunc func,
                     gpointer data,
                     GError **error)
459
{
460
  GThreadWin32 *thread;
461
  GRealThread *base_thread;
462
  guint ignore;
463
  const gchar *message = NULL;
464
  int thread_prio;
465

466
  thread = g_slice_new0 (GThreadWin32);
467
  thread->proxy = proxy;
468
  thread->handle = (HANDLE) NULL;
469
470
471
472
473
474
475
  base_thread = (GRealThread*)thread;
  base_thread->ref_count = 2;
  base_thread->ours = TRUE;
  base_thread->thread.joinable = TRUE;
  base_thread->thread.func = func;
  base_thread->thread.data = data;
  base_thread->name = g_strdup (name);
476

477
478
  thread->handle = (HANDLE) _beginthreadex (NULL, stack_size, g_thread_win32_proxy, thread,
                                            CREATE_SUSPENDED, &ignore);
479

Allison Karlitskaya's avatar
Allison Karlitskaya committed
480
  if (thread->handle == NULL)
481
    {
482
483
484
485
486
487
488
489
490
491
492
493
494
      message = "Error creating thread";
      goto error;
    }

  /* For thread priority inheritance we need to manually set the thread
   * priority of the new thread to the priority of the current thread. We
   * also have to start the thread suspended and resume it after actually
   * setting the priority here.
   *
   * On Windows, by default all new threads are created with NORMAL thread
   * priority.
   */

495
496
497
498
499
500
501
502
503
504
505
  if (scheduler_settings)
    {
      thread_prio = scheduler_settings->thread_prio;
    }
  else
    {
      HANDLE current_thread = GetCurrentThread ();
      thread_prio = GetThreadPriority (current_thread);
    }

  if (thread_prio == THREAD_PRIORITY_ERROR_RETURN)
506
507
508
509
510
    {
      message = "Error getting current thread priority";
      goto error;
    }

511
  if (SetThreadPriority (thread->handle, thread_prio) == 0)
512
513
514
515
516
517
518
519
520
    {
      message = "Error setting new thread priority";
      goto error;
    }

  if (ResumeThread (thread->handle) == -1)
    {
      message = "Error resuming new thread";
      goto error;
521
522
    }

523
  return (GRealThread *) thread;
524
525
526
527
528
529
530
531
532
533
534
535

error:
  {
    gchar *win_error = g_win32_error_message (GetLastError ());
    g_set_error (error, G_THREAD_ERROR, G_THREAD_ERROR_AGAIN,
                 "%s: %s", message, win_error);
    g_free (win_error);
    if (thread->handle)
      CloseHandle (thread->handle);
    g_slice_free (GThreadWin32, thread);
    return NULL;
  }
536
537
}

538
539
void
g_thread_yield (void)
540
541
542
543
{
  Sleep(0);
}

544
void
545
g_system_thread_wait (GRealThread *thread)
546
{
547
  GThreadWin32 *wt = (GThreadWin32 *) thread;
548

Allison Karlitskaya's avatar
Allison Karlitskaya committed
549
  win32_check_for_error (WAIT_FAILED != WaitForSingleObject (wt->handle, INFINITE));
550
551
}

552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
#define EXCEPTION_SET_THREAD_NAME ((DWORD) 0x406D1388)

#ifndef _MSC_VER
static void *SetThreadName_VEH_handle = NULL;

static LONG __stdcall
SetThreadName_VEH (PEXCEPTION_POINTERS ExceptionInfo)
{
  if (ExceptionInfo->ExceptionRecord != NULL &&
      ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SET_THREAD_NAME)
    return EXCEPTION_CONTINUE_EXECUTION;

  return EXCEPTION_CONTINUE_SEARCH;
}
#endif

typedef struct _THREADNAME_INFO
{
  DWORD  dwType;	/* must be 0x1000 */
  LPCSTR szName;	/* pointer to name (in user addr space) */
  DWORD  dwThreadID;	/* thread ID (-1=caller thread) */
  DWORD  dwFlags;	/* reserved for future use, must be zero */
} THREADNAME_INFO;

static void
SetThreadName (DWORD  dwThreadID,
               LPCSTR szThreadName)
{
   THREADNAME_INFO info;
   DWORD infosize;

   info.dwType = 0x1000;
   info.szName = szThreadName;
   info.dwThreadID = dwThreadID;
   info.dwFlags = 0;

   infosize = sizeof (info) / sizeof (DWORD);

#ifdef _MSC_VER
   __try
     {
593
594
       RaiseException (EXCEPTION_SET_THREAD_NAME, 0, infosize,
                       (const ULONG_PTR *) &info);
595
596
597
598
599
600
601
602
603
604
605
     }
   __except (EXCEPTION_EXECUTE_HANDLER)
     {
     }
#else
   /* Without a debugger we *must* have an exception handler,
    * otherwise raising an exception will crash the process.
    */
   if ((!IsDebuggerPresent ()) && (SetThreadName_VEH_handle == NULL))
     return;

606
   RaiseException (EXCEPTION_SET_THREAD_NAME, 0, infosize, (const ULONG_PTR *) &info);
607
608
609
#endif
}

610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
typedef HRESULT (WINAPI *pSetThreadDescription) (HANDLE hThread,
                                                 PCWSTR lpThreadDescription);
static pSetThreadDescription SetThreadDescriptionFunc = NULL;
HMODULE kernel32_module = NULL;

static gboolean
g_thread_win32_load_library (void)
{
  /* FIXME: Add support for UWP app */
#if !defined(G_WINAPI_ONLY_APP)
  static volatile gsize _init_once = 0;
  if (g_once_init_enter (&_init_once))
    {
      kernel32_module = LoadLibraryW (L"kernel32.dll");
      if (kernel32_module)
        {
          SetThreadDescriptionFunc =
              (pSetThreadDescription) GetProcAddress (kernel32_module,
                                                      "SetThreadDescription");
          if (!SetThreadDescriptionFunc)
            FreeLibrary (kernel32_module);
        }
      g_once_init_leave (&_init_once, 1);
    }
#endif

  return !!SetThreadDescriptionFunc;
}

static gboolean
g_thread_win32_set_thread_desc (const gchar *name)
{
  HRESULT hr;
  wchar_t *namew;

  if (!g_thread_win32_load_library () || !name)
    return FALSE;

  namew = g_utf8_to_utf16 (name, -1, NULL, NULL, NULL);
  if (!namew)
    return FALSE;

  hr = SetThreadDescriptionFunc (GetCurrentThread (), namew);

  g_free (namew);
  return SUCCEEDED (hr);
}

658
659
660
void
g_system_thread_set_name (const gchar *name)
{
661
662
663
664
665
  /* Prefer SetThreadDescription over exception based way if available,
   * since thread description set by SetThreadDescription will be preserved
   * in dump file */
  if (!g_thread_win32_set_thread_desc (name))
    SetThreadName ((DWORD) -1, name);
666
667
}

668
669
/* {{{1 Epilogue */

670
void
671
g_thread_win32_init (void)
672
{
673
  InitializeCriticalSection (&g_private_lock);
674
675
676
677
678
679
680
681

#ifndef _MSC_VER
  SetThreadName_VEH_handle = AddVectoredExceptionHandler (1, &SetThreadName_VEH);
  if (SetThreadName_VEH_handle == NULL)
    {
      /* This is bad, but what can we do? */
    }
#endif
682
683
}

684
void
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
g_thread_win32_thread_detach (void)
{
  gboolean dtors_called;

  do
    {
      GPrivateDestructor *dtor;

      /* We go by the POSIX book on this one.
       *
       * If we call a destructor then there is a chance that some new
       * TLS variables got set by code called in that destructor.
       *
       * Loop until nothing is left.
       */
      dtors_called = FALSE;

702
      for (dtor = g_atomic_pointer_get (&g_private_destructors); dtor; dtor = dtor->next)
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
        {
          gpointer value;

          value = TlsGetValue (dtor->index);
          if (value != NULL && dtor->notify != NULL)
            {
              /* POSIX says to clear this before the call */
              TlsSetValue (dtor->index, NULL);
              dtor->notify (value);
              dtors_called = TRUE;
            }
        }
    }
  while (dtors_called);
}

719
720
721
722
723
724
725
726
727
728
729
730
void
g_thread_win32_process_detach (void)
{
#ifndef _MSC_VER
  if (SetThreadName_VEH_handle != NULL)
    {
      RemoveVectoredExceptionHandler (SetThreadName_VEH_handle);
      SetThreadName_VEH_handle = NULL;
    }
#endif
}

731
/* vim:set foldmethod=marker: */