Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature #20290] Add API for C extensions to free memory #10055

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -7207,6 +7207,7 @@ gc.$(OBJEXT): $(top_srcdir)/internal/gc.h
gc.$(OBJEXT): $(top_srcdir)/internal/hash.h
gc.$(OBJEXT): $(top_srcdir)/internal/imemo.h
gc.$(OBJEXT): $(top_srcdir)/internal/io.h
gc.$(OBJEXT): $(top_srcdir)/internal/load.h
gc.$(OBJEXT): $(top_srcdir)/internal/numeric.h
gc.$(OBJEXT): $(top_srcdir)/internal/object.h
gc.$(OBJEXT): $(top_srcdir)/internal/proc.h
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,7 @@ AS_IF([test "$GCC" = yes], [
AC_CACHE_CHECK(for exported function attribute, rb_cv_func_exported, [
rb_cv_func_exported=no
RUBY_WERROR_FLAG([
for mac in '__attribute__ ((__visibility__("default")))' '__declspec(dllexport)'; do
for mac in '__declspec(dllexport)' '__attribute__ ((__visibility__("default")))'; do
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@define RUBY_FUNC_EXPORTED $mac extern
RUBY_FUNC_EXPORTED void conftest_attribute_check(void);]], [[]])],
[rb_cv_func_exported="$mac"; break])
Expand Down
23 changes: 23 additions & 0 deletions dln.c
Original file line number Diff line number Diff line change
Expand Up @@ -540,3 +540,26 @@ dln_load(const char *file)

return 0; /* dummy return */
}

#define DESTRUCT_FUNC_PREFIX EXTERNAL_PREFIX"Destruct_"

void
dln_unload(const char *file, void *handle)
{
#if defined(_WIN32) || defined(USE_DLN_DLOPEN)
struct string_part extname = init_funcname_len(file);
const size_t plen = sizeof(DESTRUCT_FUNC_PREFIX) - 1;
char *const destruct_func_name = ALLOCA_N(char, plen + extname.len + 1);
memcpy(destruct_func_name, DESTRUCT_FUNC_PREFIX, plen);
memcpy(destruct_func_name + plen, extname.ptr, extname.len);
destruct_func_name[plen + extname.len] = '\0';

void *symbol = dln_sym(handle, destruct_func_name);

if (symbol) {
((void (*)(void))symbol)();
}
#else
dln_notimplement();
#endif
}
1 change: 1 addition & 0 deletions dln.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ char *dln_find_exe_r(const char*,const char*,char*,size_t DLN_FIND_EXTRA_ARG_DEC
char *dln_find_file_r(const char*,const char*,char*,size_t DLN_FIND_EXTRA_ARG_DECL);
void *dln_load(const char*);
void *dln_symbol(void*,const char*);
void dln_unload(const char *file, void *handle);

RUBY_SYMBOL_EXPORT_END

Expand Down
6 changes: 6 additions & 0 deletions dmydln.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@ dln_symbol(void *handle, const char *symbol)

UNREACHABLE_RETURN(NULL);
}

NORETURN(void dln_unload(const char *file, void *handle));
void dln_unload(const char *file, void *handle)
{
rb_loaderror("this executable file can't load extension libraries");
}
12 changes: 12 additions & 0 deletions ext/-test-/load/destruct/destruct.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include <stdio.h>
#include <ruby/ruby.h>

void
Init_destruct(void)
{}

RUBY_FUNC_EXPORTED void
Destruct_destruct(void)
{
printf("Calling Destruct_destruct\n");
}
1 change: 1 addition & 0 deletions ext/-test-/load/destruct/extconf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
create_makefile("-test-/load/destruct")
41 changes: 33 additions & 8 deletions gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
#include "internal/hash.h"
#include "internal/imemo.h"
#include "internal/io.h"
#include "internal/load.h"
#include "internal/numeric.h"
#include "internal/object.h"
#include "internal/proc.h"
Expand Down Expand Up @@ -4275,7 +4276,6 @@ rb_objspace_free_objects(rb_objspace_t *objspace)
}
}


void
rb_objspace_call_finalizer(rb_objspace_t *objspace)
{
Expand Down Expand Up @@ -4340,14 +4340,7 @@ rb_objspace_call_finalizer(rb_objspace_t *objspace)
case T_FILE:
obj_free(objspace, vp);
break;
case T_SYMBOL:
case T_ARRAY:
case T_NONE:
break;
default:
if (rb_free_at_exit) {
obj_free(objspace, vp);
}
break;
}
if (poisoned) {
Expand All @@ -4359,6 +4352,38 @@ rb_objspace_call_finalizer(rb_objspace_t *objspace)

gc_exit(objspace, gc_enter_event_finalizer, &lock_lev);

if (rb_free_at_exit) {
rb_ext_call_destruct();

for (i = 0; i < heap_allocated_pages; i++) {
struct heap_page *page = heap_pages_sorted[i];
short stride = page->slot_size;

uintptr_t p = (uintptr_t)page->start;
uintptr_t pend = p + page->total_slots * stride;
for (; p < pend; p += stride) {
VALUE vp = (VALUE)p;
void *poisoned = asan_unpoison_object_temporary(vp);
switch (BUILTIN_TYPE(vp)) {
case T_DATA:
case T_FILE:
case T_SYMBOL:
case T_ARRAY:
case T_NONE:
case T_ZOMBIE:
break;
default:
obj_free(objspace, vp);
break;
}
if (poisoned) {
GC_ASSERT(BUILTIN_TYPE(vp) == T_NONE);
asan_poison_object(vp);
}
}
}
}

finalize_deferred_heap_pages(objspace);

st_free_table(finalizer_table);
Expand Down
6 changes: 5 additions & 1 deletion include/ruby/internal/dllexport.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@
#endif

#ifndef RUBY_FUNC_EXPORTED
# define RUBY_FUNC_EXPORTED /* void */
# ifdef _WIN32
# define RUBY_FUNC_EXPORTED __declspec(dllexport)
# else
# define RUBY_FUNC_EXPORTED /* void */
# endif
#endif

/** @endcond */
Expand Down
1 change: 1 addition & 0 deletions internal/load.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
VALUE rb_get_expanded_load_path(void);
int rb_require_internal(VALUE fname);
NORETURN(void rb_load_fail(VALUE, const char*));
void rb_ext_call_destruct(void);

#endif /* INTERNAL_LOAD_H */
14 changes: 14 additions & 0 deletions load.c
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,20 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol)
return dln_symbol((void *)NUM2SVALUE(handle), symbol);
}

static int
rb_ext_call_destruct_i(VALUE path, VALUE handle, VALUE _arg)
{
dln_unload(RSTRING_PTR(path), (void *)NUM2SVALUE(handle));

return ST_CONTINUE;
}

void
rb_ext_call_destruct(void)
{
rb_hash_foreach(ruby_dln_libmap, rb_ext_call_destruct_i, 0);
}

void
Init_load(void)
{
Expand Down
2 changes: 1 addition & 1 deletion parse.y
Original file line number Diff line number Diff line change
Expand Up @@ -1616,7 +1616,7 @@ static void numparam_pop(struct parser_params *p, NODE *prev_inner);
#define RE_OPTION_ARG_ENCODING_NONE 32

#define yytnamerr(yyres, yystr) (YYSIZE_T)rb_yytnamerr(p, yyres, yystr)
size_t rb_yytnamerr(struct parser_params *p, char *yyres, const char *yystr);
RUBY_FUNC_EXPORTED size_t rb_yytnamerr(struct parser_params *p, char *yyres, const char *yystr);

#define TOKEN2ID(tok) ( \
tTOKEN_LOCAL_BEGIN<(tok)&&(tok)<tTOKEN_LOCAL_END ? TOKEN2LOCALID(tok) : \
Expand Down
2 changes: 1 addition & 1 deletion prism/extension.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ VALUE pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *

void Init_prism_api_node(void);
void Init_prism_pack(void);
PRISM_EXPORTED_FUNCTION void Init_prism(void);
RUBY_FUNC_EXPORTED void Init_prism(void);

#endif
11 changes: 11 additions & 0 deletions test/-ext-/load/test_destruct.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "test/unit"

class TestDestruct < Test::Unit::TestCase
def test_destruct_func_not_called
assert_in_out_err(["-r-test-/load/destruct"], "", [])
end

def test_destruct_func_called_when_free_on_exit
assert_in_out_err([{"RUBY_FREE_AT_EXIT" => "1"}, "-W0", "-r-test-/load/destruct"], "puts 'Running Ruby'", ["Running Ruby", "Calling Destruct_destruct"])
end
end
Loading