Defer unloading box-local extension DLLs on Windows

box_entry_free could run before other T_DATA objects are finalized in
rb_objspace_call_finalizer, and FreeLibrary there unmapped the
rb_data_type_t and dfree functions that later finalization still
dereferences, crashing any boxed process that leaves extension objects
alive at exit. Keep the copied DLLs loaded until ruby_vm_destruct, then
unload and delete them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hiroshi SHIBATAandClaude Fable 5 authored and Hiroshi SHIBATA committed 2026-08-27 10:42:13 +09:00
1 parent 3e1ea919f4
commit 1c664d8024
4 files changed
+41 -15
Notes: git 2026-08-27 01:42:46 +00:00

No files matched your search

+36 -15
View File
@@ -65,7 +65,6 @@ bool ruby_box_crashed = false; // extern, changed only in vm.c
VALUE rb_resolve_feature_path(VALUE klass, VALUE fname);
static VALUE rb_box_inspect(VALUE obj);
static void cleanup_all_local_extensions(VALUE libmap);
void
rb_box_set_gem_flags(rb_box_gem_flags_t *flags)
@@ -303,8 +302,6 @@ box_entry_free(void *ptr)
st_foreach(box->classext_cow_classes, free_classext_for_box, (st_data_t)box);
}
cleanup_all_local_extensions(box->ruby_dln_libmap);
free_box_st_tables(ptr);
SIZED_FREE(box);
}
@@ -808,24 +805,48 @@ rb_box_cleanup_local_extension(VALUE cleanup)
(void)p;
}
static int
cleanup_local_extension_i(VALUE key, VALUE value, VALUE arg)
#if defined(_WIN32)
struct box_local_ext_list {
struct box_local_ext_list *next;
HMODULE handle;
};
static struct box_local_ext_list *box_local_exts;
#endif
void
rb_box_defer_unload_local_extension(void *handle)
{
#if defined(_WIN32)
HMODULE h = (HMODULE)NUM2PTR(value);
WCHAR module_path[MAXPATHLEN];
DWORD len = GetModuleFileNameW(h, module_path, numberof(module_path));
FreeLibrary(h);
if (len > 0 && len < numberof(module_path)) DeleteFileW(module_path);
/* A box-local copy of an extension DLL must stay loaded as long as
* objects created by it can be finalized; unloading is deferred to
* rb_box_unload_local_extensions after the objspace is destructed.
* The loaded copy cannot be deleted on Windows, so the file is also
* removed there instead of in box_ext_cleanup_free. */
struct box_local_ext_list *ext = malloc(sizeof(struct box_local_ext_list));
if (!ext) return;
ext->handle = (HMODULE)handle;
ext->next = box_local_exts;
box_local_exts = ext;
#endif
return ST_DELETE;
}
static void
cleanup_all_local_extensions(VALUE libmap)
void
rb_box_unload_local_extensions(void)
{
rb_hash_foreach(libmap, cleanup_local_extension_i, 0);
#if defined(_WIN32)
struct box_local_ext_list *ext = box_local_exts;
box_local_exts = NULL;
while (ext) {
struct box_local_ext_list *next = ext->next;
WCHAR module_path[MAXPATHLEN];
DWORD len = GetModuleFileNameW(ext->handle, module_path, numberof(module_path));
FreeLibrary(ext->handle);
if (len > 0 && len < numberof(module_path)) DeleteFileW(module_path);
free(ext);
ext = next;
}
#endif
}
VALUE
+2
View File
@@ -90,6 +90,8 @@ VALUE rb_get_box_object(rb_box_t *ns);
VALUE rb_box_local_extension(VALUE box, VALUE fname, VALUE path, VALUE *cleanup);
void rb_box_cleanup_local_extension(VALUE cleanup);
void rb_box_defer_unload_local_extension(void *handle);
void rb_box_unload_local_extensions(void);
void rb_initialize_mandatory_boxes(void);
void rb_box_init_done(void);
+1
View File
@@ -1224,6 +1224,7 @@ load_ext(VALUE path, VALUE fname)
void *handle = dln_load_feature(RSTRING_PTR(loaded), RSTRING_PTR(fname));
if (cleanup) {
rb_box_cleanup_local_extension(cleanup);
rb_box_defer_unload_local_extension(handle);
}
RB_GC_GUARD(loaded);
RB_GC_GUARD(fname);
+2
View File
@@ -3584,6 +3584,8 @@ ruby_vm_destruct(rb_vm_t *vm)
rb_yjit_free_at_exit();
#endif
}
rb_box_unload_local_extensions();
}
RUBY_FREE_LEAVE("vm");
return 0;