#include <string.h>
#define GC_I_HIDE_POINTERS
-#include <mono/metadata/gc-internal.h>
+#include <mono/metadata/gc-internals.h>
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/profiler-private.h>
#include <mono/metadata/class-internals.h>
#include <mono/metadata/runtime.h>
#include <mono/metadata/sgen-toggleref.h>
#include <mono/utils/atomic.h>
-#include <mono/utils/mono-logger-internal.h>
+#include <mono/utils/mono-logger-internals.h>
#include <mono/utils/mono-memory-model.h>
#include <mono/utils/mono-time.h>
#include <mono/utils/mono-threads.h>
#include <mono/utils/dtrace.h>
#include <mono/utils/gc_wrapper.h>
-#include <mono/utils/mono-mutex.h>
+#include <mono/utils/mono-os-mutex.h>
#include <mono/utils/mono-counters.h>
#if HAVE_BOEHM_GC
#define BOEHM_GC_BIT_FINALIZER_AWARE 1
static MonoGCFinalizerCallbacks fin_callbacks;
+/* GC Handles */
+
+static mono_mutex_t handle_section;
+#define lock_handles(handles) mono_os_mutex_lock (&handle_section)
+#define unlock_handles(handles) mono_os_mutex_unlock (&handle_section)
+
+typedef struct {
+ guint32 *bitmap;
+ gpointer *entries;
+ guint32 size;
+ guint8 type;
+ guint slot_hint : 24; /* starting slot for search in bitmap */
+ /* 2^16 appdomains should be enough for everyone (though I know I'll regret this in 20 years) */
+ /* we alloc this only for weak refs, since we can get the domain directly in the other cases */
+ guint16 *domain_ids;
+} HandleData;
+
+#define EMPTY_HANDLE_DATA(type) {NULL, NULL, 0, (type), 0, NULL}
+
+/* weak and weak-track arrays will be allocated in malloc memory
+ */
+static HandleData gc_handles [] = {
+ EMPTY_HANDLE_DATA (HANDLE_WEAK),
+ EMPTY_HANDLE_DATA (HANDLE_WEAK_TRACK),
+ EMPTY_HANDLE_DATA (HANDLE_NORMAL),
+ EMPTY_HANDLE_DATA (HANDLE_PINNED)
+};
+
static void
mono_gc_warning (char *msg, GC_word arg)
{
for (char **ptr = opts; ptr && *ptr; ptr ++) {
char *opt = *ptr;
if (!strcmp (opt, "do-not-finalize")) {
- do_not_finalize = 1;
+ mono_do_not_finalize = 1;
} else if (!strcmp (opt, "log-finalizers")) {
log_finalizers = 1;
}
GC_finalizer_notifier = mono_gc_finalize_notify;
GC_init_gcj_malloc (5, NULL);
+ GC_allow_register_threads ();
if ((env = g_getenv ("MONO_GC_PARAMS"))) {
char **ptr, **opts = g_strsplit (env, ",", -1);
cb.mono_method_is_critical = (gpointer)mono_runtime_is_critical_method;
mono_threads_init (&cb, sizeof (MonoThreadInfo));
- mono_mutex_init (&mono_gc_lock);
+ mono_os_mutex_init (&mono_gc_lock);
+ mono_os_mutex_init_recursive (&handle_section);
mono_thread_info_attach (&dummy);
mono_gc_enable_events ();
+
+ MONO_GC_REGISTER_ROOT_FIXED (gc_handles [HANDLE_NORMAL].entries, MONO_ROOT_SOURCE_GC_HANDLE, "gc handles table");
+ MONO_GC_REGISTER_ROOT_FIXED (gc_handles [HANDLE_PINNED].entries, MONO_ROOT_SOURCE_GC_HANDLE, "gc handles table");
+
gc_initialized = TRUE;
}
return GC_thread_is_registered ();
}
-extern int GC_thread_register_foreign (void *base_addr);
-
gboolean
mono_gc_register_thread (void *baseptr)
{
static void*
boehm_thread_register (MonoThreadInfo* info, void *baseptr)
{
- if (mono_gc_is_gc_thread())
- return info;
-#if !defined(HOST_WIN32)
- return GC_thread_register_foreign (baseptr) ? info : NULL;
-#else
- return NULL;
-#endif
+ struct GC_stack_base sb;
+ int res;
+
+ /* TODO: use GC_get_stack_base instead of baseptr. */
+ sb.mem_base = baseptr;
+ res = GC_register_my_thread (&sb);
+ if (res == GC_UNIMPLEMENTED)
+ return NULL; /* Cannot happen with GC v7+. */
+ return info;
}
static void
static gint64 gc_start_time;
static void
-on_gc_notification (GCEventType event)
+on_gc_notification (GC_EventType event)
{
MonoGCEvent e = (MonoGCEvent)event;
void
mono_gc_enable_events (void)
{
- GC_notify_event = on_gc_notification;
+ GC_set_on_collection_event (on_gc_notification);
GC_on_heap_resize = on_gc_heap_resize;
}
}
int
-mono_gc_register_root (char *start, size_t size, void *descr)
+mono_gc_register_root (char *start, size_t size, void *descr, MonoGCRootSource source, const char *msg)
{
/* for some strange reason, they want one extra byte on the end */
GC_add_roots (start, start + size + 1);
#endif
}
-void
+static void
mono_gc_weak_link_add (void **link_addr, MonoObject *obj, gboolean track)
{
/* libgc requires that we use HIDE_POINTER... */
GC_GENERAL_REGISTER_DISAPPEARING_LINK (link_addr, obj);
}
-void
+static void
mono_gc_weak_link_remove (void **link_addr, gboolean track)
{
if (track)
return REVEAL_POINTER (*link_a);
}
-MonoObject*
+static MonoObject *
mono_gc_weak_link_get (void **link_addr)
{
MonoObject *obj = GC_call_with_alloc_lock (reveal_link, link_addr);
}
void*
-mono_gc_alloc_fixed (size_t size, void *descr)
+mono_gc_alloc_fixed (size_t size, void *descr, MonoGCRootSource source, const char *msg)
{
/* To help track down typed allocation bugs */
/*
int
mono_gc_get_restart_signal (void)
{
- return GC_get_restart_signal ();
+ return GC_get_thr_restart_signal ();
}
#if defined(USE_COMPILER_TLS) && defined(__linux__) && (defined(__i386__) || defined(__x86_64__))
MonoMethod *res;
MonoMethodSignature *csig;
const char *name = NULL;
- AllocatorWrapperInfo *info;
+ WrapperInfo *info;
if (atype == ATYPE_FREEPTR) {
name = slowpath ? "SlowAllocPtrfree" : "AllocPtrfree";
mono_mb_emit_byte (mb, 0x0D); /* CEE_MONO_TLS */
mono_mb_emit_i4 (mb, tls_key);
if (atype == ATYPE_FREEPTR || atype == ATYPE_FREEPTR_FOR_BOX || atype == ATYPE_STRING)
- mono_mb_emit_icon (mb, G_STRUCT_OFFSET (struct GC_Thread_Rep, ptrfree_freelists));
+ mono_mb_emit_icon (mb, G_STRUCT_OFFSET (struct GC_Thread_Rep, tlfs)
+ + G_STRUCT_OFFSET (struct thread_local_freelists,
+ ptrfree_freelists));
else if (atype == ATYPE_NORMAL)
- mono_mb_emit_icon (mb, G_STRUCT_OFFSET (struct GC_Thread_Rep, normal_freelists));
+ mono_mb_emit_icon (mb, G_STRUCT_OFFSET (struct GC_Thread_Rep, tlfs)
+ + G_STRUCT_OFFSET (struct thread_local_freelists,
+ normal_freelists));
else if (atype == ATYPE_GCJ)
- mono_mb_emit_icon (mb, G_STRUCT_OFFSET (struct GC_Thread_Rep, gcj_freelists));
+ mono_mb_emit_icon (mb, G_STRUCT_OFFSET (struct GC_Thread_Rep, tlfs)
+ + G_STRUCT_OFFSET (struct thread_local_freelists,
+ gcj_freelists));
else
g_assert_not_reached ();
mono_mb_emit_byte (mb, MONO_CEE_ADD);
mono_mb_emit_byte (mb, MONO_CEE_RET);
- res = mono_mb_create_method (mb, csig, 8);
+ info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NONE);
+ info->d.alloc.gc_name = "boehm";
+ info->d.alloc.alloc_type = atype;
+
+ res = mono_mb_create (mb, csig, 8, info);
mono_mb_free (mb);
mono_method_get_header (res)->init_locals = FALSE;
- info = mono_image_alloc0 (mono_defaults.corlib, sizeof (AllocatorWrapperInfo));
- info->gc_name = "boehm";
- info->alloc_type = atype;
- mono_marshal_set_wrapper_info (res, info);
-
return res;
}
return res;
res = create_allocator (atype, TLS_KEY_BOEHM_GC_THREAD, slowpath);
- mono_mutex_lock (&mono_gc_lock);
+ mono_os_mutex_lock (&mono_gc_lock);
if (cache [atype]) {
mono_free_method (res);
res = cache [atype];
mono_memory_barrier ();
cache [atype] = res;
}
- mono_mutex_unlock (&mono_gc_lock);
+ mono_os_mutex_unlock (&mono_gc_lock);
return res;
}
#endif
guint
-mono_gc_get_vtable_bits (MonoClass *class)
+mono_gc_get_vtable_bits (MonoClass *klass)
{
if (fin_callbacks.is_class_finalization_aware) {
- if (fin_callbacks.is_class_finalization_aware (class))
+ if (fin_callbacks.is_class_finalization_aware (klass))
return BOEHM_GC_BIT_FINALIZER_AWARE;
}
return 0;
void
mono_gc_toggleref_add (MonoObject *object, mono_bool strong_ref)
{
- GC_toggleref_add ((GC_PTR)object, (int)strong_ref);
+ if (GC_toggleref_add ((GC_PTR)object, (int)strong_ref) != GC_SUCCESS)
+ g_error ("GC_toggleref_add failed\n");
}
void
mono_gc_toggleref_register_callback (MonoToggleRefStatus (*proccess_toggleref) (MonoObject *obj))
{
- GC_toggleref_register_callback ((int (*) (GC_PTR obj)) proccess_toggleref);
+ GC_set_toggleref_func ((GC_ToggleRefStatus (*) (GC_PTR obj)) proccess_toggleref);
}
/* Test support code */
fin_callbacks = *callbacks;
- GC_set_finalizer_notify_proc ((void (*) (GC_PTR))fin_notifier);
+ GC_set_await_finalize_proc ((void (*) (GC_PTR))fin_notifier);
+}
+
+#define BITMAP_SIZE (sizeof (*((HandleData *)NULL)->bitmap) * CHAR_BIT)
+
+static inline gboolean
+slot_occupied (HandleData *handles, guint slot) {
+ return handles->bitmap [slot / BITMAP_SIZE] & (1 << (slot % BITMAP_SIZE));
+}
+
+static inline void
+vacate_slot (HandleData *handles, guint slot) {
+ handles->bitmap [slot / BITMAP_SIZE] &= ~(1 << (slot % BITMAP_SIZE));
+}
+
+static inline void
+occupy_slot (HandleData *handles, guint slot) {
+ handles->bitmap [slot / BITMAP_SIZE] |= 1 << (slot % BITMAP_SIZE);
+}
+
+static int
+find_first_unset (guint32 bitmap)
+{
+ int i;
+ for (i = 0; i < 32; ++i) {
+ if (!(bitmap & (1 << i)))
+ return i;
+ }
+ return -1;
+}
+
+static void
+handle_data_alloc_entries (HandleData *handles)
+{
+ handles->size = 32;
+ if (MONO_GC_HANDLE_TYPE_IS_WEAK (handles->type)) {
+ handles->entries = g_malloc0 (sizeof (*handles->entries) * handles->size);
+ handles->domain_ids = g_malloc0 (sizeof (*handles->domain_ids) * handles->size);
+ } else {
+ handles->entries = mono_gc_alloc_fixed (sizeof (*handles->entries) * handles->size, NULL, MONO_ROOT_SOURCE_GC_HANDLE, "gc handles table");
+ }
+ handles->bitmap = g_malloc0 (handles->size / CHAR_BIT);
+}
+
+static gint
+handle_data_next_unset (HandleData *handles)
+{
+ gint slot;
+ for (slot = handles->slot_hint; slot < handles->size / BITMAP_SIZE; ++slot) {
+ if (handles->bitmap [slot] == 0xffffffff)
+ continue;
+ handles->slot_hint = slot;
+ return find_first_unset (handles->bitmap [slot]);
+ }
+ return -1;
+}
+
+static gint
+handle_data_first_unset (HandleData *handles)
+{
+ gint slot;
+ for (slot = 0; slot < handles->slot_hint; ++slot) {
+ if (handles->bitmap [slot] == 0xffffffff)
+ continue;
+ handles->slot_hint = slot;
+ return find_first_unset (handles->bitmap [slot]);
+ }
+ return -1;
+}
+
+/* Returns the index of the current slot in the bitmap. */
+static void
+handle_data_grow (HandleData *handles, gboolean track)
+{
+ guint32 *new_bitmap;
+ guint32 new_size = handles->size * 2; /* always double: we memset to 0 based on this below */
+
+ /* resize and copy the bitmap */
+ new_bitmap = g_malloc0 (new_size / CHAR_BIT);
+ memcpy (new_bitmap, handles->bitmap, handles->size / CHAR_BIT);
+ g_free (handles->bitmap);
+ handles->bitmap = new_bitmap;
+
+ /* resize and copy the entries */
+ if (MONO_GC_HANDLE_TYPE_IS_WEAK (handles->type)) {
+ gpointer *entries;
+ guint16 *domain_ids;
+ gint i;
+ domain_ids = g_malloc0 (sizeof (*handles->domain_ids) * new_size);
+ entries = g_malloc0 (sizeof (*handles->entries) * new_size);
+ memcpy (domain_ids, handles->domain_ids, sizeof (*handles->domain_ids) * handles->size);
+ for (i = 0; i < handles->size; ++i) {
+ MonoObject *obj = mono_gc_weak_link_get (&(handles->entries [i]));
+ if (obj) {
+ mono_gc_weak_link_add (&(entries [i]), obj, track);
+ mono_gc_weak_link_remove (&(handles->entries [i]), track);
+ } else {
+ g_assert (!handles->entries [i]);
+ }
+ }
+ g_free (handles->entries);
+ g_free (handles->domain_ids);
+ handles->entries = entries;
+ handles->domain_ids = domain_ids;
+ } else {
+ gpointer *entries;
+ entries = mono_gc_alloc_fixed (sizeof (*handles->entries) * new_size, NULL, MONO_ROOT_SOURCE_GC_HANDLE, "gc handles table");
+ mono_gc_memmove_aligned (entries, handles->entries, sizeof (*handles->entries) * handles->size);
+ mono_gc_free_fixed (handles->entries);
+ handles->entries = entries;
+ }
+ handles->slot_hint = handles->size / BITMAP_SIZE;
+ handles->size = new_size;
+}
+
+static guint32
+alloc_handle (HandleData *handles, MonoObject *obj, gboolean track)
+{
+ gint slot, i;
+ guint32 res;
+ lock_handles (handles);
+ if (!handles->size)
+ handle_data_alloc_entries (handles);
+ i = handle_data_next_unset (handles);
+ if (i == -1 && handles->slot_hint != 0)
+ i = handle_data_first_unset (handles);
+ if (i == -1) {
+ handle_data_grow (handles, track);
+ i = 0;
+ }
+ slot = handles->slot_hint * BITMAP_SIZE + i;
+ occupy_slot (handles, slot);
+ handles->entries [slot] = NULL;
+ if (MONO_GC_HANDLE_TYPE_IS_WEAK (handles->type)) {
+ /*FIXME, what to use when obj == null?*/
+ handles->domain_ids [slot] = (obj ? mono_object_get_domain (obj) : mono_domain_get ())->domain_id;
+ if (obj)
+ mono_gc_weak_link_add (&(handles->entries [slot]), obj, track);
+ } else {
+ handles->entries [slot] = obj;
+ }
+
+#ifndef DISABLE_PERFCOUNTERS
+ mono_perfcounters->gc_num_handles++;
+#endif
+ unlock_handles (handles);
+ res = MONO_GC_HANDLE (slot, handles->type);
+ mono_profiler_gc_handle (MONO_PROFILER_GC_HANDLE_CREATED, handles->type, res, obj);
+ return res;
+}
+
+/**
+ * mono_gchandle_new:
+ * @obj: managed object to get a handle for
+ * @pinned: whether the object should be pinned
+ *
+ * This returns a handle that wraps the object, this is used to keep a
+ * reference to a managed object from the unmanaged world and preventing the
+ * object from being disposed.
+ *
+ * If @pinned is false the address of the object can not be obtained, if it is
+ * true the address of the object can be obtained. This will also pin the
+ * object so it will not be possible by a moving garbage collector to move the
+ * object.
+ *
+ * Returns: a handle that can be used to access the object from
+ * unmanaged code.
+ */
+guint32
+mono_gchandle_new (MonoObject *obj, gboolean pinned)
+{
+ return alloc_handle (&gc_handles [pinned? HANDLE_PINNED: HANDLE_NORMAL], obj, FALSE);
+}
+
+/**
+ * mono_gchandle_new_weakref:
+ * @obj: managed object to get a handle for
+ * @pinned: whether the object should be pinned
+ *
+ * This returns a weak handle that wraps the object, this is used to
+ * keep a reference to a managed object from the unmanaged world.
+ * Unlike the mono_gchandle_new the object can be reclaimed by the
+ * garbage collector. In this case the value of the GCHandle will be
+ * set to zero.
+ *
+ * If @pinned is false the address of the object can not be obtained, if it is
+ * true the address of the object can be obtained. This will also pin the
+ * object so it will not be possible by a moving garbage collector to move the
+ * object.
+ *
+ * Returns: a handle that can be used to access the object from
+ * unmanaged code.
+ */
+guint32
+mono_gchandle_new_weakref (MonoObject *obj, gboolean track_resurrection)
+{
+ return alloc_handle (&gc_handles [track_resurrection? HANDLE_WEAK_TRACK: HANDLE_WEAK], obj, track_resurrection);
+}
+
+/**
+ * mono_gchandle_get_target:
+ * @gchandle: a GCHandle's handle.
+ *
+ * The handle was previously created by calling mono_gchandle_new or
+ * mono_gchandle_new_weakref.
+ *
+ * Returns a pointer to the MonoObject represented by the handle or
+ * NULL for a collected object if using a weakref handle.
+ */
+MonoObject*
+mono_gchandle_get_target (guint32 gchandle)
+{
+ guint slot = MONO_GC_HANDLE_SLOT (gchandle);
+ guint type = MONO_GC_HANDLE_TYPE (gchandle);
+ HandleData *handles = &gc_handles [type];
+ MonoObject *obj = NULL;
+ if (type >= HANDLE_TYPE_MAX)
+ return NULL;
+
+ lock_handles (handles);
+ if (slot < handles->size && slot_occupied (handles, slot)) {
+ if (MONO_GC_HANDLE_TYPE_IS_WEAK (handles->type)) {
+ obj = mono_gc_weak_link_get (&handles->entries [slot]);
+ } else {
+ obj = handles->entries [slot];
+ }
+ } else {
+ /* print a warning? */
+ }
+ unlock_handles (handles);
+ /*g_print ("get target of entry %d of type %d: %p\n", slot, handles->type, obj);*/
+ return obj;
+}
+
+void
+mono_gchandle_set_target (guint32 gchandle, MonoObject *obj)
+{
+ guint slot = MONO_GC_HANDLE_SLOT (gchandle);
+ guint type = MONO_GC_HANDLE_TYPE (gchandle);
+ HandleData *handles = &gc_handles [type];
+ MonoObject *old_obj = NULL;
+
+ g_assert (type < HANDLE_TYPE_MAX);
+ lock_handles (handles);
+ if (slot < handles->size && slot_occupied (handles, slot)) {
+ if (MONO_GC_HANDLE_TYPE_IS_WEAK (handles->type)) {
+ old_obj = handles->entries [slot];
+ if (handles->entries [slot])
+ mono_gc_weak_link_remove (&handles->entries [slot], handles->type == HANDLE_WEAK_TRACK);
+ if (obj)
+ mono_gc_weak_link_add (&handles->entries [slot], obj, handles->type == HANDLE_WEAK_TRACK);
+ /*FIXME, what to use when obj == null?*/
+ handles->domain_ids [slot] = (obj ? mono_object_get_domain (obj) : mono_domain_get ())->domain_id;
+ } else {
+ handles->entries [slot] = obj;
+ }
+ } else {
+ /* print a warning? */
+ }
+ /*g_print ("changed entry %d of type %d to object %p (in slot: %p)\n", slot, handles->type, obj, handles->entries [slot]);*/
+ unlock_handles (handles);
}
+/**
+ * mono_gchandle_is_in_domain:
+ * @gchandle: a GCHandle's handle.
+ * @domain: An application domain.
+ *
+ * Returns: true if the object wrapped by the @gchandle belongs to the specific @domain.
+ */
gboolean
mono_gc_is_null (void)
{
return FALSE;
}
+gboolean
+mono_gchandle_is_in_domain (guint32 gchandle, MonoDomain *domain)
+{
+ guint slot = MONO_GC_HANDLE_SLOT (gchandle);
+ guint type = MONO_GC_HANDLE_TYPE (gchandle);
+ HandleData *handles = &gc_handles [type];
+ gboolean result = FALSE;
+
+ if (type >= HANDLE_TYPE_MAX)
+ return FALSE;
+
+ lock_handles (handles);
+ if (slot < handles->size && slot_occupied (handles, slot)) {
+ if (MONO_GC_HANDLE_TYPE_IS_WEAK (handles->type)) {
+ result = domain->domain_id == handles->domain_ids [slot];
+ } else {
+ MonoObject *obj;
+ obj = handles->entries [slot];
+ if (obj == NULL)
+ result = TRUE;
+ else
+ result = domain == mono_object_domain (obj);
+ }
+ } else {
+ /* print a warning? */
+ }
+ unlock_handles (handles);
+ return result;
+}
+
+/**
+ * mono_gchandle_free:
+ * @gchandle: a GCHandle's handle.
+ *
+ * Frees the @gchandle handle. If there are no outstanding
+ * references, the garbage collector can reclaim the memory of the
+ * object wrapped.
+ */
+void
+mono_gchandle_free (guint32 gchandle)
+{
+ guint slot = MONO_GC_HANDLE_SLOT (gchandle);
+ guint type = MONO_GC_HANDLE_TYPE (gchandle);
+ HandleData *handles = &gc_handles [type];
+ if (type >= HANDLE_TYPE_MAX)
+ return;
+
+ lock_handles (handles);
+ if (slot < handles->size && slot_occupied (handles, slot)) {
+ if (MONO_GC_HANDLE_TYPE_IS_WEAK (handles->type)) {
+ if (handles->entries [slot])
+ mono_gc_weak_link_remove (&handles->entries [slot], handles->type == HANDLE_WEAK_TRACK);
+ } else {
+ handles->entries [slot] = NULL;
+ }
+ vacate_slot (handles, slot);
+ } else {
+ /* print a warning? */
+ }
+#ifndef DISABLE_PERFCOUNTERS
+ mono_perfcounters->gc_num_handles--;
+#endif
+ /*g_print ("freed entry %d of type %d\n", slot, handles->type);*/
+ unlock_handles (handles);
+ mono_profiler_gc_handle (MONO_PROFILER_GC_HANDLE_DESTROYED, handles->type, gchandle, NULL);
+}
+
+/**
+ * mono_gchandle_free_domain:
+ * @domain: domain that is unloading
+ *
+ * Function used internally to cleanup any GC handle for objects belonging
+ * to the specified domain during appdomain unload.
+ */
+void
+mono_gchandle_free_domain (MonoDomain *domain)
+{
+ guint type;
+
+ for (type = HANDLE_TYPE_MIN; type < HANDLE_PINNED; ++type) {
+ guint slot;
+ HandleData *handles = &gc_handles [type];
+ lock_handles (handles);
+ for (slot = 0; slot < handles->size; ++slot) {
+ if (!slot_occupied (handles, slot))
+ continue;
+ if (MONO_GC_HANDLE_TYPE_IS_WEAK (type)) {
+ if (domain->domain_id == handles->domain_ids [slot]) {
+ vacate_slot (handles, slot);
+ if (handles->entries [slot])
+ mono_gc_weak_link_remove (&handles->entries [slot], handles->type == HANDLE_WEAK_TRACK);
+ }
+ } else {
+ if (handles->entries [slot] && mono_object_domain (handles->entries [slot]) == domain) {
+ vacate_slot (handles, slot);
+ handles->entries [slot] = NULL;
+ }
+ }
+ }
+ unlock_handles (handles);
+ }
+
+}
+
#endif /* no Boehm GC */