Skip to content

Commit

Permalink
Free everything at shutdown
Browse files Browse the repository at this point in the history
when the RUBY_FREE_ON_SHUTDOWN environment variable is set, manually free memory at shutdown.

Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Co-authored-by: Peter Zhu <peter@peterzhu.ca>
  • Loading branch information
3 people committed Dec 7, 2023
1 parent b361a80 commit 6816e8e
Show file tree
Hide file tree
Showing 27 changed files with 285 additions and 6 deletions.
6 changes: 6 additions & 0 deletions builtin.c
Expand Up @@ -57,6 +57,12 @@ rb_load_with_builtin_functions(const char *feature_name, const struct rb_builtin

#endif

void
rb_free_loaded_builtin_table(void)
{
// do nothing
}

void
Init_builtin(void)
{
Expand Down
2 changes: 2 additions & 0 deletions common.mk
Expand Up @@ -18700,6 +18700,7 @@ vm.$(OBJEXT): $(top_srcdir)/internal/compar.h
vm.$(OBJEXT): $(top_srcdir)/internal/compile.h
vm.$(OBJEXT): $(top_srcdir)/internal/compilers.h
vm.$(OBJEXT): $(top_srcdir)/internal/cont.h
vm.$(OBJEXT): $(top_srcdir)/internal/encoding.h
vm.$(OBJEXT): $(top_srcdir)/internal/error.h
vm.$(OBJEXT): $(top_srcdir)/internal/eval.h
vm.$(OBJEXT): $(top_srcdir)/internal/fixnum.h
Expand All @@ -18721,6 +18722,7 @@ vm.$(OBJEXT): $(top_srcdir)/internal/string.h
vm.$(OBJEXT): $(top_srcdir)/internal/struct.h
vm.$(OBJEXT): $(top_srcdir)/internal/symbol.h
vm.$(OBJEXT): $(top_srcdir)/internal/thread.h
vm.$(OBJEXT): $(top_srcdir)/internal/transcode.h
vm.$(OBJEXT): $(top_srcdir)/internal/variable.h
vm.$(OBJEXT): $(top_srcdir)/internal/vm.h
vm.$(OBJEXT): $(top_srcdir)/internal/warnings.h
Expand Down
6 changes: 6 additions & 0 deletions cont.c
Expand Up @@ -275,6 +275,12 @@ struct rb_fiber_struct {

static struct fiber_pool shared_fiber_pool = {NULL, NULL, 0, 0, 0, 0};

void
rb_free_shared_fiber_pool(void)
{
xfree(shared_fiber_pool.allocations);
}

static ID fiber_initialize_keywords[3] = {0};

/*
Expand Down
18 changes: 18 additions & 0 deletions encoding.c
Expand Up @@ -71,6 +71,24 @@ static struct enc_table {
st_table *names;
} global_enc_table;

static int
enc_names_free_i(st_data_t name, st_data_t idx, st_data_t args)
{
ruby_xfree((void *)name);
return ST_DELETE;
}

void
rb_free_global_enc_table(void)
{
for (size_t i = 0; i < ENCODING_LIST_CAPA; i++) {
xfree((void *)global_enc_table.list[i].enc);
}

st_foreach(global_enc_table.names, enc_names_free_i, (st_data_t)0);
st_free_table(global_enc_table.names);
}

static rb_encoding *global_enc_ascii,
*global_enc_utf_8,
*global_enc_us_ascii;
Expand Down
8 changes: 8 additions & 0 deletions error.c
Expand Up @@ -2693,6 +2693,14 @@ syntax_error_with_path(VALUE exc, VALUE path, VALUE *mesg, rb_encoding *enc)

static st_table *syserr_tbl;

void
rb_free_warning(void)
{
st_free_table(warning_categories.id2enum);
st_free_table(warning_categories.enum2id);
st_free_table(syserr_tbl);
}

static VALUE
set_syserr(int n, const char *name)
{
Expand Down
36 changes: 36 additions & 0 deletions gc.c
Expand Up @@ -4582,6 +4582,35 @@ force_chain_object(st_data_t key, st_data_t val, st_data_t arg)

bool rb_obj_is_main_ractor(VALUE gv);

void
rb_objspace_free_objects(rb_objspace_t *objspace)
{
for (size_t 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;
switch (BUILTIN_TYPE(vp)) {
case T_DATA: {
if (rb_obj_is_mutex(vp) || rb_obj_is_thread(vp)) {
rb_data_free(objspace, vp);
}
break;
}
case T_ARRAY:
obj_free(objspace, vp);
break;
default:
break;
}
}
}
}


void
rb_objspace_call_finalizer(rb_objspace_t *objspace)
{
Expand Down Expand Up @@ -4648,7 +4677,14 @@ rb_objspace_call_finalizer(rb_objspace_t *objspace)
make_io_zombie(objspace, vp);
}
break;
case T_SYMBOL:
case T_ARRAY:
case T_NONE:
break;
default:
if (rb_free_on_exit) {
obj_free(objspace, vp);
}
break;
}
if (poisoned) {
Expand Down
3 changes: 3 additions & 0 deletions internal/cont.h
Expand Up @@ -22,6 +22,9 @@ void rb_jit_cont_init(void);
void rb_jit_cont_each_iseq(rb_iseq_callback callback, void *data);
void rb_jit_cont_finish(void);

/* vm.c */
void rb_free_shared_fiber_pool(void);

// Copy locals from the current execution to the specified fiber.
VALUE rb_fiber_inherit_storage(struct rb_execution_context_struct *ec, struct rb_fiber_struct *fiber);

Expand Down
3 changes: 3 additions & 0 deletions internal/encoding.h
Expand Up @@ -29,4 +29,7 @@ void rb_enc_set_base(const char *name, const char *orig);
int rb_enc_set_dummy(int index);
PUREFUNC(int rb_data_is_encoding(VALUE obj));

/* vm.c */
void rb_free_global_enc_table(void);

#endif /* INTERNAL_ENCODING_H */
3 changes: 3 additions & 0 deletions internal/error.h
Expand Up @@ -169,6 +169,9 @@ VALUE rb_syserr_new_path_in(const char *func_name, int n, VALUE path);
#endif
RUBY_SYMBOL_EXPORT_END

/* vm.c */
void rb_free_warning(void);

static inline void
rb_raise_cstr_i(VALUE etype, VALUE mesg)
{
Expand Down
1 change: 1 addition & 0 deletions internal/random.h
Expand Up @@ -12,5 +12,6 @@

/* random.c */
int ruby_fill_random_bytes(void *, size_t, int);
void rb_free_default_rand_key(void);

#endif /* INTERNAL_RANDOM_H */
3 changes: 3 additions & 0 deletions internal/symbol.h
Expand Up @@ -32,6 +32,9 @@ ID rb_make_temporary_id(size_t n);
void rb_gc_free_dsymbol(VALUE);
int rb_static_id_valid_p(ID id);

/* vm.c */
void rb_free_static_symid_str(void);

#if __has_builtin(__builtin_constant_p)
#define rb_sym_intern_ascii_cstr(ptr) \
(__builtin_constant_p(ptr) ? \
Expand Down
3 changes: 3 additions & 0 deletions internal/transcode.h
Expand Up @@ -17,4 +17,7 @@
extern VALUE rb_cEncodingConverter;
size_t rb_econv_memsize(rb_econv_t *);

/* vm.c */
void rb_free_transcoder_table(void);

#endif /* INTERNAL_TRANSCODE_H */
5 changes: 5 additions & 0 deletions internal/vm.h
Expand Up @@ -83,6 +83,11 @@ void rb_check_stack_overflow(void);
extern uint64_t rb_vm_insns_count;
#endif

extern bool rb_free_on_exit;

/* miniinit.c and builtin.c */
void rb_free_loaded_builtin_table(void);

/* vm_insnhelper.c */
VALUE rb_equal_opt(VALUE obj1, VALUE obj2);
VALUE rb_eql_opt(VALUE obj1, VALUE obj2);
Expand Down
6 changes: 6 additions & 0 deletions iseq.c
Expand Up @@ -3422,6 +3422,12 @@ typedef struct insn_data_struct {
} insn_data_t;
static insn_data_t insn_data[VM_INSTRUCTION_SIZE/2];

void
rb_free_encoded_insn_data(void)
{
st_free_table(encoded_insn_data);
}

void
rb_vm_encoded_insn_data_table_init(void)
{
Expand Down
2 changes: 2 additions & 0 deletions iseq.h
Expand Up @@ -329,6 +329,8 @@ VALUE rb_iseq_local_variables(const rb_iseq_t *iseq);

attr_index_t rb_estimate_iv_count(VALUE klass, const rb_iseq_t * initialize_iseq);

void rb_free_encoded_insn_data(void);

RUBY_SYMBOL_EXPORT_END

#endif /* RUBY_ISEQ_H */
7 changes: 7 additions & 0 deletions load.c
Expand Up @@ -360,6 +360,13 @@ loaded_features_index_clear_i(st_data_t key, st_data_t val, st_data_t arg)
return ST_DELETE;
}

void
rb_free_loaded_features_index(rb_vm_t *vm)
{
st_foreach(vm->loaded_features_index, loaded_features_index_clear_i, 0);
st_free_table(vm->loaded_features_index);
}

static st_table *
get_loaded_features_index(rb_vm_t *vm)
{
Expand Down
16 changes: 15 additions & 1 deletion marshal.c
Expand Up @@ -2517,6 +2517,20 @@ Init_marshal(void)
rb_define_const(rb_mMarshal, "MINOR_VERSION", INT2FIX(MARSHAL_MINOR));
}

static int
free_compat_i(st_data_t key, st_data_t value, st_data_t _)
{
xfree((marshal_compat_t *)value);
return ST_CONTINUE;
}

static void
free_compat_allocator_table(void *data)
{
st_foreach(data, free_compat_i, 0);
st_free_table(data);
}

static st_table *
compat_allocator_table(void)
{
Expand All @@ -2525,7 +2539,7 @@ compat_allocator_table(void)
#undef RUBY_UNTYPED_DATA_WARNING
#define RUBY_UNTYPED_DATA_WARNING 0
compat_allocator_tbl_wrapper =
Data_Wrap_Struct(0, mark_marshal_compat_t, 0, compat_allocator_tbl);
Data_Wrap_Struct(0, mark_marshal_compat_t, free_compat_allocator_table, compat_allocator_tbl);
rb_gc_register_mark_object(compat_allocator_tbl_wrapper);
return compat_allocator_tbl;
}
Expand Down
7 changes: 7 additions & 0 deletions miniinit.c
Expand Up @@ -49,3 +49,10 @@ Init_enc(void)
}

#include "mini_builtin.c"

void
rb_free_loaded_builtin_table(void)
{
if (loaded_builtin_table)
st_free_table(loaded_builtin_table);
}
1 change: 1 addition & 0 deletions missing/setproctitle.c
Expand Up @@ -135,6 +135,7 @@ compat_init_setproctitle(int argc, char *argv[])
}

#ifndef HAVE_SETPROCTITLE

void
setproctitle(const char *fmt, ...)
{
Expand Down
6 changes: 6 additions & 0 deletions random.c
Expand Up @@ -157,6 +157,12 @@ rand_start(rb_random_mt_t *r)

static rb_ractor_local_key_t default_rand_key;

void
rb_free_default_rand_key(void)
{
xfree(default_rand_key);
}

static void
default_rand_mark(void *ptr)
{
Expand Down
5 changes: 5 additions & 0 deletions ruby.c
Expand Up @@ -1754,6 +1754,11 @@ ruby_opt_init(ruby_cmdline_options_t *opt)
"environment variables RUBY_GC_HEAP_%d_INIT_SLOTS");
}

if (getenv("RUBY_FREE_ON_EXIT")) {
rb_warn("Free on exit is experimental and may be unstable");
rb_free_on_exit = true;
}

#if USE_RJIT
// rb_call_builtin_inits depends on RubyVM::RJIT.enabled?
if (opt->rjit.on)
Expand Down
10 changes: 10 additions & 0 deletions symbol.c
Expand Up @@ -531,6 +531,16 @@ register_sym(rb_symbols_t *symbols, VALUE str, VALUE sym)
}
}

void
rb_free_static_symid_str(void)
{
GLOBAL_SYMBOLS_ENTER(symbols)
{
st_free_table(symbols->str_sym);
}
GLOBAL_SYMBOLS_LEAVE();
}

static void
unregister_sym(rb_symbols_t *symbols, VALUE str, VALUE sym)
{
Expand Down
6 changes: 6 additions & 0 deletions test/ruby/test_rubyoptions.rb
Expand Up @@ -1227,4 +1227,10 @@ def test_null_script
omit "#{IO::NULL} is not a character device" unless File.chardev?(IO::NULL)
assert_in_out_err([IO::NULL], success: true)
end

def test_free_on_exit_env_var
env = {"RUBY_FREE_ON_EXIT"=>"1"}
assert_ruby_status([env, "-e;"])
assert_in_out_err([env, "-W"], "", [], /Free on exit is experimental and may be unstable/)
end
end
22 changes: 22 additions & 0 deletions transcode.c
Expand Up @@ -181,6 +181,28 @@ typedef struct {

static st_table *transcoder_table;

static int
free_inner_transcode_i(st_data_t key, st_data_t val, st_data_t arg)
{
xfree((void *)val);
return ST_DELETE;
}

static int
free_transcode_i(st_data_t key, st_data_t val, st_data_t arg)
{
st_foreach((void *)val, free_inner_transcode_i, 0);
st_free_table((void *)val);
return ST_DELETE;
}

void
rb_free_transcoder_table(void)
{
st_foreach(transcoder_table, free_transcode_i, 0);
st_free_table(transcoder_table);
}

static transcoder_entry_t *
make_transcoder_entry(const char *sname, const char *dname)
{
Expand Down
27 changes: 27 additions & 0 deletions variable.c
Expand Up @@ -438,6 +438,33 @@ struct rb_global_entry {
bool ractor_local;
};

static enum rb_id_table_iterator_result
free_global_entry_i(ID key, VALUE val, void *arg)
{
struct rb_global_entry *entry = (struct rb_global_entry *)val;
if (entry->var->counter == 1) {
ruby_xfree(entry->var);
}
else {
entry->var->counter--;
}
ruby_xfree(entry);
return ID_TABLE_DELETE;
}

void
rb_free_rb_global_tbl(void)
{
rb_id_table_foreach(rb_global_tbl, free_global_entry_i, 0);
rb_id_table_free(rb_global_tbl);
}

void
rb_free_generic_iv_tbl_(void)
{
st_free_table(generic_iv_tbl_);
}

static struct rb_global_entry*
rb_find_global_entry(ID id)
{
Expand Down

0 comments on commit 6816e8e

Please sign in to comment.