From 85571cb7124a6193e4c6ae57bfed9c62bcb5de0c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 11 Aug 2018 03:53:36 +0000 Subject: tweak hpb stats destructor output Including the last rb_gc_count() call would be useful, and avoid showing giant SIZE_MAX initializers for acc.min fields. --- ext/mwrap/mwrap.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 138384d..b60c21d 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -1351,21 +1351,26 @@ static void dump_hpb(FILE *fp, unsigned flags) if (flags & DUMP_HPB_STATS) { fprintf(fp, "lifespan_max: %zu\n" - "lifespan_min: %zu\n" + "lifespan_min:%s%zu\n" "lifespan_mean: %0.3f\n" "lifespan_stddev: %0.3f\n" "deathspan_max: %zu\n" - "deathspan_min: %zu\n" + "deathspan_min:%s%zu\n" "deathspan_mean: %0.3f\n" - "deathspan_stddev: %0.3f\n", + "deathspan_stddev: %0.3f\n" + "gc_count: %zu\n", hpb_stats.alive.max, + hpb_stats.alive.min == SIZE_MAX ? " -" : " ", hpb_stats.alive.min, hpb_stats.alive.mean, acc_stddev_dbl(&hpb_stats.alive), hpb_stats.reborn.max, + hpb_stats.reborn.min == SIZE_MAX ? " -" : " ", hpb_stats.reborn.min, hpb_stats.reborn.mean, - acc_stddev_dbl(&hpb_stats.reborn)); + acc_stddev_dbl(&hpb_stats.reborn), + /* n.b.: unsafe to call rb_gc_count() in destructor */ + generation); } if (flags & DUMP_HPB_EACH) { struct alloc_hdr *h; -- cgit v1.2.3-24-ge0c7 From a58386266d4effd90efa840fb0b7d6aa5ce71748 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 11 Aug 2018 03:53:39 +0000 Subject: struct acc: use 64-bit counters Since there's no hope of atomicity here anyways, use 64-bit counters. --- ext/mwrap/mwrap.c | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index b60c21d..cbda436 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -218,14 +218,14 @@ static int has_ec_p(void) } struct acc { - size_t nr; - size_t min; - size_t max; + uint64_t nr; + int64_t min; + int64_t max; double m2; double mean; }; -#define ACC_INIT(name) { .nr=0, .min=SIZE_MAX, .max=0, .m2=0, .mean=0 } +#define ACC_INIT(name) { .nr=0, .min=INT64_MAX, .max=-1, .m2=0, .mean=0 } /* for tracking 16K-aligned heap page bodies (protected by GVL) */ struct { @@ -314,29 +314,35 @@ static void acc_add(struct acc *acc, size_t val) { double delta = val - acc->mean; - size_t nr = ++acc->nr; + uint64_t nr = ++acc->nr; - /* 32-bit overflow, ignore accuracy, just don't divide-by-zero */ + /* just don't divide-by-zero if we ever hit this (unlikely :P) */ if (nr) acc->mean += delta / nr; acc->m2 += delta * (val - acc->mean); - if (val < acc->min) - acc->min = val; - if (val > acc->max) - acc->max = val; + if ((int64_t)val < acc->min) + acc->min = (int64_t)val; + if ((int64_t)val > acc->max) + acc->max = (int64_t)val; } +#if SIZEOF_LONG == 8 +# define INT64toNUM(x) LONG2NUM((long)x) +#elif defined(HAVE_LONG_LONG) && SIZEOF_LONG_LONG == 8 +# define INT64toNUM(x) LL2NUM((LONG_LONG)x) +#endif + static VALUE acc_max(const struct acc *acc) { - return acc->max ? SIZET2NUM(acc->max) : DBL2NUM(HUGE_VAL); + return INT64toNUM(acc->max); } static VALUE acc_min(const struct acc *acc) { - return acc->min == SIZE_MAX ? DBL2NUM(HUGE_VAL) : SIZET2NUM(acc->min); + return acc->min == INT64_MAX ? INT2FIX(-1) : INT64toNUM(acc->min); } static VALUE @@ -1350,22 +1356,22 @@ static void dump_hpb(FILE *fp, unsigned flags) { if (flags & DUMP_HPB_STATS) { fprintf(fp, - "lifespan_max: %zu\n" - "lifespan_min:%s%zu\n" + "lifespan_max: %"PRId64"\n" + "lifespan_min:%s%"PRId64"\n" "lifespan_mean: %0.3f\n" "lifespan_stddev: %0.3f\n" - "deathspan_max: %zu\n" - "deathspan_min:%s%zu\n" + "deathspan_max: %"PRId64"\n" + "deathspan_min:%s%"PRId64"\n" "deathspan_mean: %0.3f\n" "deathspan_stddev: %0.3f\n" "gc_count: %zu\n", hpb_stats.alive.max, - hpb_stats.alive.min == SIZE_MAX ? " -" : " ", + hpb_stats.alive.min == INT64_MAX ? " -" : " ", hpb_stats.alive.min, hpb_stats.alive.mean, acc_stddev_dbl(&hpb_stats.alive), hpb_stats.reborn.max, - hpb_stats.reborn.min == SIZE_MAX ? " -" : " ", + hpb_stats.reborn.min == INT64_MAX ? " -" : " ", hpb_stats.reborn.min, hpb_stats.reborn.mean, acc_stddev_dbl(&hpb_stats.reborn), -- cgit v1.2.3-24-ge0c7 From f2155aca16e5786a1d1189d5eb10458111b78014 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 11 Aug 2018 04:04:42 +0000 Subject: doc: 2.1 pre-release updates Mwrap.total_bytes_allocated/total_bytes_freed API needs docs, and we need to clarify heap page stats are not reset. --- ext/mwrap/mwrap.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index cbda436..2ee04a5 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -1193,11 +1193,17 @@ static VALUE mwrap_quiet(VALUE mod) return rb_ensure(rb_yield, SIZET2NUM(cur), reset_locating, 0); } +/* + * total bytes allocated as tracked by mwrap + */ static VALUE total_inc(VALUE mod) { return SIZET2NUM(total_bytes_inc); } +/* + * total bytes freed as tracked by mwrap + */ static VALUE total_dec(VALUE mod) { return SIZET2NUM(total_bytes_dec); @@ -1338,6 +1344,10 @@ void Init_mwrap(void) * from posix_memalign(3) use in mainline Ruby: * * https://sourceware.org/bugzilla/show_bug.cgi?id=14581 + * + * These statistics are never reset by Mwrap.reset or + * any other method. They only make sense in the context + * of an entire program lifetime. */ hpb = rb_define_class_under(mod, "HeapPageBody", rb_cObject); rb_define_singleton_method(hpb, "stat", hpb_stat, -1); -- cgit v1.2.3-24-ge0c7 From 5120b83117179f81c7c73c41c122da0f6f5cbdb7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 11 Aug 2018 04:09:57 +0000 Subject: mwrap 2.1.0 - heap_page_body struct tracking This release enables tracking of memalign allocations for "struct heap_page_body" in the Ruby GC. This can be useful for tracking deathspans (time between free and re-allocation) of heap page bodies which can cause fragmentation in some malloc implementations, including glibc. The documentation for it is available at: https://80x24.org/mwrap/Mwrap/HeapPageBody.html And a live demo runs at: https://80x24.org/MWRAP/heap_pages This release also includes global counters for Mwrap.total_bytes_allocated and Mwrap.total_bytes_freed 10 changes since v2.0.0 (2018-07-20): add olddoc.yml to generate links in page footers add .olddoc.yml to MANIFEST gemspec: use "git describe" output for prereleases add global counters for total bytes allocated/freed keep stats for memalign-ed heap_page_body in Ruby remove "memalign:" MWRAP option allow dump_heap: mask via MWRAP env tweak hpb stats destructor output struct acc: use 64-bit counters doc: 2.1 pre-release updates --- mwrap.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mwrap.gemspec b/mwrap.gemspec index 2c01a68..877c80d 100644 --- a/mwrap.gemspec +++ b/mwrap.gemspec @@ -12,7 +12,7 @@ desc = `git describe --abbrev=4 HEAD`.strip.tr('-', '.').delete_prefix('v') Gem::Specification.new do |s| s.name = 'mwrap' - s.version = desc.empty? ? '2.0.0' : desc + s.version = desc.empty? ? '2.1.0' : desc s.homepage = 'https://80x24.org/mwrap/' s.authors = ["Ruby hackers"] s.summary = 'LD_PRELOAD malloc wrapper for Ruby' -- cgit v1.2.3-24-ge0c7 From 4a90540056afce4f73da97300e6709993355fe4f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 9 Aug 2022 00:19:11 +0000 Subject: memalign: perform rcu_read_unlock on ENOMEM We must not forget to release RCU read locks even if the process will probably die, soon, due to ENOMEM. I noticed this while working on the Perl5/XS port. Link: https://80x24.org/mwrap-perl/20191102020331.28050-4-e@80x24.org/ --- ext/mwrap/mwrap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 2ee04a5..4575e34 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -632,9 +632,9 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller) p = ptr_align(p, alignment); h = ptr2hdr(p); alloc_insert_rcu(l, h, size, real); - update_stats_rcu_unlock(l); *pp = p; } + update_stats_rcu_unlock(l); } return real ? 0 : ENOMEM; -- cgit v1.2.3-24-ge0c7 From 32c9a817cab2e28534de62ff62c4686b520fb3fe Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 15 Aug 2022 21:22:17 +0000 Subject: workaround breakage from urcu v0.11.4 urcu v0.11.4+ introduced commit 7ca7fe9c03 (Make temporary variable in _rcu_dereference non-const, 2021-07-29) which conflicts with our use of _LGPL_SOURCE. In retrospect, CMM_LOAD_SHARED and CMM_STORE_SHARED seem sufficient for our use of the `totals' cds_lfht pointer since the constructur should always fire before any threads are running. This is fixed in urcu v0.12.4 and v0.13.2 (released 2022-08-18) but I suspect older versions will live on in enterprise/LTS distros for a long while. Link: https://lore.kernel.org/lttng-dev/20220809181927.GA3718@dcvr/ --- ext/mwrap/mwrap.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 4575e34..477b1cb 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -139,8 +139,8 @@ __attribute__((constructor)) static void resolve_malloc(void) _exit(1); } #endif /* !FreeBSD */ - totals = lfht_new(); - if (!totals) + CMM_STORE_SHARED(totals, lfht_new()); + if (!CMM_LOAD_SHARED(totals)) fprintf(stderr, "failed to allocate totals table\n"); err = pthread_atfork(call_rcu_before_fork, @@ -375,7 +375,7 @@ static struct src_loc *totals_add_rcu(struct src_loc *k) struct cds_lfht *t; again: - t = rcu_dereference(totals); + t = CMM_LOAD_SHARED(totals); if (!t) goto out_unlock; cds_lfht_lookup(t, k->hval, loc_eq, k, &iter); cur = cds_lfht_iter_get_node(&iter); @@ -417,7 +417,7 @@ static struct src_loc *update_stats_rcu_lock(size_t size, uintptr_t caller) static const size_t xlen = sizeof(caller); char *dst; - if (caa_unlikely(!totals)) return 0; + if (caa_unlikely(!CMM_LOAD_SHARED(totals))) return 0; if (locating++) goto out; /* do not recurse into another *alloc */ uatomic_add(&total_bytes_inc, size); @@ -808,7 +808,7 @@ static void *dump_to_file(void *x) ++locating; rcu_read_lock(); - t = rcu_dereference(totals); + t = CMM_LOAD_SHARED(totals); if (!t) goto out_unlock; cds_lfht_for_each_entry(t, &iter, l, hnode) { @@ -877,7 +877,7 @@ static void *totals_reset(void *ign) uatomic_set(&total_bytes_dec, 0); rcu_read_lock(); - t = rcu_dereference(totals); + t = CMM_LOAD_SHARED(totals); cds_lfht_for_each_entry(t, &iter, l, hnode) { uatomic_set(&l->total, 0); uatomic_set(&l->allocations, 0); @@ -945,7 +945,7 @@ static VALUE dump_each_rcu(VALUE x) struct cds_lfht_iter iter; struct src_loc *l; - t = rcu_dereference(totals); + t = CMM_LOAD_SHARED(totals); cds_lfht_for_each_entry(t, &iter, l, hnode) { VALUE v[6]; if (l->total <= a->min) continue; @@ -1049,9 +1049,9 @@ static VALUE mwrap_aref(VALUE mod, VALUE loc) if (!k) return val; + t = CMM_LOAD_SHARED(totals); + if (!t) return val; rcu_read_lock(); - t = rcu_dereference(totals); - if (!t) goto out_unlock; cds_lfht_lookup(t, k->hval, loc_eq, k, &iter); cur = cds_lfht_iter_get_node(&iter); @@ -1059,7 +1059,6 @@ static VALUE mwrap_aref(VALUE mod, VALUE loc) l = caa_container_of(cur, struct src_loc, hnode); val = TypedData_Wrap_Struct(cSrcLoc, &src_loc_type, l); } -out_unlock: rcu_read_unlock(); return val; } -- cgit v1.2.3-24-ge0c7 From a90e237d93256ded3088c83520683fc5835da72d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 20 Aug 2022 21:35:21 +0000 Subject: constify arg for totals_add_rcu It's not modified by that function, so constify it for ease-of-reading and review. --- ext/mwrap/mwrap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 477b1cb..8592ea7 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -367,7 +367,7 @@ acc_stddev(const struct acc *acc) return DBL2NUM(acc_stddev_dbl(acc)); } -static struct src_loc *totals_add_rcu(struct src_loc *k) +static struct src_loc *totals_add_rcu(const struct src_loc *k) { struct cds_lfht_iter iter; struct cds_lfht_node *cur; -- cgit v1.2.3-24-ge0c7 From 7ec73b9ccdeaed30e813884f7765281be422bc21 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 20 Aug 2022 21:35:22 +0000 Subject: support Ruby 3.0.x HEAP_PAGE_SIZE no longer estimates malloc overhead in Ruby 3.0.x, but we can now rely on the GC::INTERNAL_CONSTANTS hash to access the true value. We also need to account for Ractors in 3.0+, and thus we need to rely on thread-specific `ruby_current_ec' instead of process-wide `ruby_current_execution_context_ptr' (which no longer exists in 3.0+). Finally, the VM seems prone to making some small immortal allocations in a few places we were not expecting in 2.7 and earlier. Account for that and loosen some exact checks to account for it. Tested on Ruby 3.0.3 and 3.0.4 --- ext/mwrap/extconf.rb | 7 +++++++ ext/mwrap/mwrap.c | 25 +++++++++++++++++++++---- test/test_mwrap.rb | 27 ++++++++++++++++----------- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/ext/mwrap/extconf.rb b/ext/mwrap/extconf.rb index e9dbb1e..254a3bb 100644 --- a/ext/mwrap/extconf.rb +++ b/ext/mwrap/extconf.rb @@ -25,4 +25,11 @@ else abort 'missing __builtin_add_overflow' end +begin + if n = GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE] + $defs << "-DHEAP_PAGE_SIZE=#{n}" + end +rescue NameError +end + create_makefile 'mwrap' diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 8592ea7..cd68eed 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -3,7 +3,7 @@ * License: GPL-2.0+ */ #define _LGPL_SOURCE /* allows URCU to inline some stuff */ -#include +#include /* defines HAVE_RUBY_RACTOR_H on 3.0+ */ #include #include #include @@ -22,10 +22,24 @@ #include #include "jhash.h" +#if __STDC_VERSION__ >= 201112 +# define MWRAP_TSD _Thread_local +#elif defined(__GNUC__) +# define MWRAP_TSD __thread +#else +# error _Thread_local nor __thread supported +#endif + static ID id_uminus; const char *rb_source_location_cstr(int *line); /* requires 2.6.0dev */ extern int __attribute__((weak)) ruby_thread_has_gvl_p(void); + +#ifdef HAVE_RUBY_RACTOR_H /* Ruby 3.0+ */ +extern MWRAP_TSD void * __attribute__((weak)) ruby_current_ec; +#else /* Ruby 2.6-2.7 */ extern void * __attribute__((weak)) ruby_current_execution_context_ptr; +# define ruby_current_ec ruby_current_execution_context_ptr +#endif extern void * __attribute__((weak)) ruby_current_vm_ptr; /* for rb_gc_count */ extern size_t __attribute__((weak)) rb_gc_count(void); extern VALUE __attribute__((weak)) rb_cObject; @@ -40,9 +54,12 @@ static size_t total_bytes_inc, total_bytes_dec; /* match values in Ruby gc.c */ #define HEAP_PAGE_ALIGN_LOG 14 enum { - HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG), + HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG) +#ifndef HEAP_PAGE_SIZE /* Ruby 2.6-2.7 only */ + , REQUIRED_SIZE_BY_MALLOC = (sizeof(size_t) * 5), HEAP_PAGE_SIZE = (HEAP_PAGE_ALIGN - REQUIRED_SIZE_BY_MALLOC) +#endif }; #define IS_HEAP_PAGE_BODY ((struct src_loc *)-1) @@ -75,7 +92,7 @@ static int resolving_malloc; } \ } while (0) -static __thread size_t locating; +static MWRAP_TSD size_t locating; static size_t generation; static size_t page_size; static struct cds_lfht *totals; @@ -214,7 +231,7 @@ static char *int2str(int num, char *dst, size_t * size) static int has_ec_p(void) { return (ruby_thread_has_gvl_p() && ruby_current_vm_ptr && - ruby_current_execution_context_ptr); + ruby_current_ec); } struct acc { diff --git a/test/test_mwrap.rb b/test/test_mwrap.rb index 48fba23..fadaef6 100644 --- a/test/test_mwrap.rb +++ b/test/test_mwrap.rb @@ -29,7 +29,8 @@ class TestMwrap < Test::Unit::TestCase tmp.rewind lines = tmp.readlines line_1 = lines.grep(/\s-e:1\b/)[0].strip - assert_equal '10001', line_1.split(/\s+/)[0] + bytes = line_1.split(/\s+/)[0].to_i + assert_operator bytes, :>=, 10001 end end @@ -42,7 +43,7 @@ class TestMwrap < Test::Unit::TestCase res = system(env, *cmd, { 5 => tmp }) assert res, $?.inspect tmp.rewind - assert_match(/\b10001\s+1\s+-e:1$/, tmp.read) + assert_match(/\b1\d{4}\s+[1-9]\d*\s+-e:1$/, tmp.read) env['MWRAP'] = 'dump_fd:1,dump_min:10000' tmp.rewind @@ -50,14 +51,14 @@ class TestMwrap < Test::Unit::TestCase res = system(env, *cmd, { 1 => tmp }) assert res, $?.inspect tmp.rewind - assert_match(/\b10001\s+1\s+-e:1$/, tmp.read) + assert_match(/\b1\d{4}\s+[1-9]\d*\s+-e:1$/, tmp.read) tmp.rewind tmp.truncate(0) env['MWRAP'] = "dump_path:#{tmp.path},dump_min:10000" res = system(env, *cmd) assert res, $?.inspect - assert_match(/\b10001\s+1\s+-e:1$/, tmp.read) + assert_match(/\b1\d{4}\s+[1-9]\d*\s+-e:1$/, tmp.read) tmp.rewind tmp.truncate(0) @@ -98,7 +99,7 @@ class TestMwrap < Test::Unit::TestCase tmp.rewind buf = tmp.read assert_not_match(/\s+-e:1$/, buf) - assert_match(/\b20001\s+1\s+-e:3$/, buf) + assert_match(/\b2\d{4}\s+[0-9]\d*\s+-e:3$/, buf) end end @@ -176,8 +177,8 @@ class TestMwrap < Test::Unit::TestCase -e GC.disable -e keep=("0"*10000) -e loc=Mwrap["-e:3"] - -e loc.each{|size,gen|p([size,gen,count])} - ) + -e + ) + [ 'loc.each{|size,gen|p([size,gen,count]) if size > 10000}' ] buf = IO.popen(@@env, cmd, &:read) assert_predicate $?, :success? assert_match(/\A\[\s*\d+,\s*\d+,\s*\d+\]\s*\z/s, buf) @@ -230,7 +231,8 @@ class TestMwrap < Test::Unit::TestCase loc.name == k or abort 'SourceLocation#name broken' loc.total >= 10000 or abort 'SourceLocation#total broken' loc.frees == 0 or abort 'SourceLocation#frees broken' - loc.allocations == 1 or abort 'SourceLocation#allocations broken' + loc.allocations >= 1 or + abort "SourceLocation#allocations broken: #{loc.allocations}" seen = false loc.each do |*x| seen = x end seen[1] == loc.total or 'SourceLocation#each broken' @@ -240,7 +242,9 @@ class TestMwrap < Test::Unit::TestCase freed = false until freed freed = true - loc.each do freed = false end + loc.each do |size, gen| + freed = false if size >= 10000 + end end loc.frees == 1 or abort 'SourceLocation#frees broken (after free)' Float === loc.mean_lifespan or abort 'mean_lifespan broken' @@ -264,8 +268,9 @@ class TestMwrap < Test::Unit::TestCase assert_separately(+"#{<<~"begin;"}\n#{<<~'end;'}") begin; require 'mwrap' - before = __LINE__ + before = nil res = Mwrap.quiet do |depth| + before = __LINE__ depth == 1 or abort 'depth is not 1' ('a' * 10000).clear Mwrap.quiet { |d| d == 2 or abort 'depth is not 2' } @@ -304,7 +309,7 @@ class TestMwrap < Test::Unit::TestCase gen <= GC.count && gen >= 0 or abort "bad generation: #{gen}" (0 == (addr & 16383)) or abort "addr not aligned: #{'%x' % addr}" end - nr == ap or abort 'HeapPageBody.each missed page' + nr == ap or abort "HeapPageBody.each missed page #{nr} != #{ap}" 10.times { (1..20000).to_a.map(&:to_s) } 3.times { GC.start } Mwrap::HeapPageBody.stat(h) -- cgit v1.2.3-24-ge0c7 From aa82056594757a75aa0f0b629a8b4612f523c5b0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 20 Aug 2022 21:35:23 +0000 Subject: disable HeapPageBody count test for Ruby 3.1 Ruby 3.1+ uses mmap on platforms relevant to us, so we currently can't account for it with various malloc wrappers. Tested on Ruby 3.1.2, this is the only change necessary to support 3.1.x so far; but the functionality is gone unless we decide to wrap mmap, as well. --- test/test_mwrap.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_mwrap.rb b/test/test_mwrap.rb index fadaef6..c506554 100644 --- a/test/test_mwrap.rb +++ b/test/test_mwrap.rb @@ -309,7 +309,9 @@ class TestMwrap < Test::Unit::TestCase gen <= GC.count && gen >= 0 or abort "bad generation: #{gen}" (0 == (addr & 16383)) or abort "addr not aligned: #{'%x' % addr}" end - nr == ap or abort "HeapPageBody.each missed page #{nr} != #{ap}" + if RUBY_VERSION.to_f < 3.1 # 3.1+ uses mmap on platforms we care about + nr == ap or abort "HeapPageBody.each missed page #{nr} != #{ap}" + end 10.times { (1..20000).to_a.map(&:to_s) } 3.times { GC.start } Mwrap::HeapPageBody.stat(h) -- cgit v1.2.3-24-ge0c7 From 0f1880761db6b1c819887ba9541e78617d61802e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 20 Aug 2022 22:55:05 +0000 Subject: quiet uninitialized and unused variable warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These warnings are already suppressed in similar ways in the Perl/XS port. The only warning which remains is related to cfree, which I'm leaving alone, for now, due to lack of use/test-cases: ../../../../ext/mwrap/mwrap.c:689:6: warning: ‘cfree’ specifies less restrictive attributes than its target ‘free’: ‘leaf’, ‘nothrow’ [-Wmissing-attributes] 689 | void cfree(void *) __attribute__((alias("free"))); | ^~~~~ /usr/include/stdlib.h:565:13: note: ‘cfree’ target declared here 565 | extern void free (void *__ptr) __THROW; | ^~~~ --- ext/mwrap/mwrap.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index cd68eed..a097b2e 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -169,11 +169,18 @@ __attribute__((constructor)) static void resolve_malloc(void) --locating; } +#ifdef NDEBUG +#define QUIET_CC_WARNING(var) (void)var; +#else +#define QUIET_CC_WARNING(var) +#endif + static void mutex_lock(pthread_mutex_t *m) { int err = pthread_mutex_lock(m); assert(err == 0); + QUIET_CC_WARNING(err) } static void @@ -181,6 +188,7 @@ mutex_unlock(pthread_mutex_t *m) { int err = pthread_mutex_unlock(m); assert(err == 0); + QUIET_CC_WARNING(err) } #ifndef HAVE_MEMPCPY @@ -660,16 +668,14 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller) static void * memalign_result(int err, void *p) { - if (caa_unlikely(err)) { + if (caa_unlikely(err)) errno = err; - return 0; - } return p; } void *memalign(size_t alignment, size_t size) { - void *p; + void *p = NULL; int err = internal_memalign(&p, alignment, size, RETURN_ADDRESS(0)); return memalign_result(err, p); } @@ -684,7 +690,7 @@ void cfree(void *) __attribute__((alias("free"))); void *valloc(size_t size) { - void *p; + void *p = NULL; int err = internal_memalign(&p, page_size, size, RETURN_ADDRESS(0)); return memalign_result(err, p); } @@ -702,7 +708,7 @@ void *valloc(size_t size) void *pvalloc(size_t size) { size_t alignment = page_size; - void *p; + void *p = NULL; int err; if (add_overflow_p(size, alignment)) { -- cgit v1.2.3-24-ge0c7 From 61f9d94e11e046094c607bdf56c9633938e074b2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Aug 2022 17:17:57 +0000 Subject: various doc updates Copyright years are unnecessary churn and probably aren't necessary: https://www.linuxfoundation.org/blog/copyright-notices-in-open-source-software-projects/ add POP3 and IMAP archive info, and drop the outdated link to the live demo since I no longer have the resources to run it. Finally, add rdoc (olddoc) + rsync support --- .document | 1 + .gitignore | 3 +++ .olddoc.yml | 4 +++- README | 12 +++++++----- Rakefile | 30 +++++++++++++++++++++++++++++- bin/mwrap | 2 +- ext/mwrap/extconf.rb | 2 +- ext/mwrap/mwrap.c | 2 +- lib/mwrap_rack.rb | 8 +++----- test/test_mwrap.rb | 2 +- 10 files changed, 50 insertions(+), 16 deletions(-) diff --git a/.document b/.document index 4ca33e3..4385cfe 100644 --- a/.document +++ b/.document @@ -1,2 +1,3 @@ ext/mwrap/mwrap.c lib/mwrap_rack.rb +README diff --git a/.gitignore b/.gitignore index aa3606c..f670b62 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ /pkg /*.gem /doc +/NEWS +/NEWS.atom.xml +/LATEST diff --git a/.olddoc.yml b/.olddoc.yml index dac0353..bfecbaf 100644 --- a/.olddoc.yml +++ b/.olddoc.yml @@ -5,4 +5,6 @@ rdoc_url: https://80x24.org/mwrap/ ml_url: https://80x24.org/mwrap-public/ public_email: mwrap-public@80x24.org nntp_url: - - nntp://news.public-inbox.org/inbox.comp.lang.ruby.mwrap +- nntps://news.public-inbox.org/inbox.comp.lang.ruby.mwrap +imap_url: +- imaps://;AUTH=ANONYMOUS@80x24.org/inbox.comp.lang.ruby.mwrap.0 diff --git a/README b/README index 3a20258..f387bc4 100644 --- a/README +++ b/README @@ -67,16 +67,18 @@ first two columns to find the hottest malloc locations. mwrap 2.0.0+ also supports a Rack application endpoint, it is documented at: - https://80x24.org/mwrap/MwrapRack.html +https://80x24.org/mwrap/MwrapRack.html == Known problems * 32-bit machines are prone to overflow (WONTFIX) -== Mail archives and list: +== Public mail archives and contact info: - https://80x24.org/mwrap-public/ - nntp://80x24.org/inbox.comp.lang.ruby.mwrap +* https://80x24.org/mwrap-public/ +* nntps://80x24.org/inbox.comp.lang.ruby.mwrap +* imaps://;AUTH=ANONYMOUS@80x24.org/inbox.comp.lang.ruby.mwrap.0 +* https://80x24.org/mwrap-public/_/text/help/#pop3 No subscription will ever be required to post, but HTML mail will be rejected: @@ -88,7 +90,7 @@ will be rejected: git clone https://80x24.org/mwrap.git Send all patches and pull requests (use "git request-pull" to format) to -the mailing list. We do not use centralized or proprietary messaging +mwrap-public@80x24.org. We do not use centralized or proprietary messaging systems. == License diff --git a/Rakefile b/Rakefile index 50bfa89..255d346 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,4 @@ -# Copyright (C) 2018 mwrap hackers +# Copyright (C) mwrap hackers # License: GPL-2.0+ require 'rake/testtask' begin @@ -14,3 +14,31 @@ task :default => :compile c_files = File.readlines('MANIFEST').grep(%r{ext/.*\.[ch]$}).map!(&:chomp!) task 'compile:mwrap' => c_files + +olddoc = ENV['OLDDOC'] || 'olddoc' +rdoc = ENV['RDOC'] || 'rdoc' +task :rsync_docs do + require 'fileutils' + top = %w(README COPYING LATEST NEWS NEWS.atom.xml) + system("git", "set-file-times") + dest = ENV["RSYNC_DEST"] || "80x24.org:/srv/80x24/mwrap/" + FileUtils.rm_rf('doc') + sh "#{olddoc} prepare" + sh "#{rdoc} -f dark216" # dark216 requires olddoc 1.7+ + File.unlink('doc/created.rid') rescue nil + File.unlink('doc/index.html') rescue nil + FileUtils.cp(top, 'doc') + sh "#{olddoc} merge" + + Dir['doc/**/*'].each do |txt| + st = File.stat(txt) + if st.file? + gz = "#{txt}.gz" + tmp = "#{gz}.#$$" + sh("gzip --rsyncable -9 <#{txt} >#{tmp}") + File.utime(st.atime, st.mtime, tmp) # make nginx gzip_static happy + File.rename(tmp, gz) + end + end + sh("rsync --chmod=Fugo=r #{ENV['RSYNC_OPT']} -av doc/ #{dest}/") +end diff --git a/bin/mwrap b/bin/mwrap index 9f67dab..212078c 100755 --- a/bin/mwrap +++ b/bin/mwrap @@ -1,6 +1,6 @@ #!/usr/bin/ruby # frozen_string_literal: true -# Copyright (C) 2018 mwrap hackers +# Copyright (C) mwrap hackers # License: GPL-2.0+ require 'mwrap' mwrap_so = $".grep(%r{/mwrap\.so\z})[0] or abort "mwrap.so not loaded" diff --git a/ext/mwrap/extconf.rb b/ext/mwrap/extconf.rb index 254a3bb..512ab82 100644 --- a/ext/mwrap/extconf.rb +++ b/ext/mwrap/extconf.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# Copyright (C) 2018 mwrap hackers +# Copyright (C) mwrap hackers # License: GPL-2.0+ require 'mkmf' diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index a097b2e..85b847b 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 mwrap hackers + * Copyright (C) mwrap hackers * License: GPL-2.0+ */ #define _LGPL_SOURCE /* allows URCU to inline some stuff */ diff --git a/lib/mwrap_rack.rb b/lib/mwrap_rack.rb index e45b26d..53380b9 100644 --- a/lib/mwrap_rack.rb +++ b/lib/mwrap_rack.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2018 all contributors +# Copyright (C) all contributors # License: GPL-2.0+ # frozen_string_literal: true require 'mwrap' @@ -17,9 +17,6 @@ require 'cgi' # map('/MWRAP') { run(MwrapRack.new) } # map('/') { run(your_normal_app) } # -# A live demo is available at https://80x24.org/MWRAP/ -# (warning the demo machine is 32-bit, so counters will overflow) -# # This module is only available in mwrap 2.0.0+ class MwrapRack module HtmlResponse # :nodoc: @@ -115,10 +112,11 @@ class MwrapRack end GC_STAT_URL = 'https://docs.ruby-lang.org/en/trunk/GC.html#method-c-stat' - GC_STAT_HELP = <<~"" + GC_STAT_HELP = <<~EOM

Non-Infinity lifespans can indicate fragmentation.

See #{GC_STAT_URL} for info on GC.stat values. + EOM def each Mwrap.quiet do diff --git a/test/test_mwrap.rb b/test/test_mwrap.rb index c506554..eaa65cb 100644 --- a/test/test_mwrap.rb +++ b/test/test_mwrap.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# Copyright (C) 2018 mwrap hackers +# Copyright (C) mwrap hackers # License: GPL-2.0+ require 'test/unit' require 'mwrap' -- cgit v1.2.3-24-ge0c7 From e3d506de0aa3e8a574db0724bcc540f4e08f3838 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Aug 2022 18:10:18 +0000 Subject: mwrap 2.2.0 This release adds Ruby 2.7, 3.0, and 3.1 support. This also fixes breakage in urcu v0.11.4, v0.12.3, and v0.13.1. While urcu v0.12.4 and v0.13.2 were released 2022-08-18, old versions will continue to be in distros for a while (and urcu v0.11.x won't be updated). There are also several updates ported from the Perl version. 7 changes since v2.1.0 in 2018: memalign: perform rcu_read_unlock on ENOMEM workaround breakage from urcu v0.11.4 constify arg for totals_add_rcu support Ruby 3.0.x disable HeapPageBody count test for Ruby 3.1 quiet uninitialized and unused variable warnings various doc updates --- mwrap.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mwrap.gemspec b/mwrap.gemspec index 877c80d..48a32b2 100644 --- a/mwrap.gemspec +++ b/mwrap.gemspec @@ -12,9 +12,9 @@ desc = `git describe --abbrev=4 HEAD`.strip.tr('-', '.').delete_prefix('v') Gem::Specification.new do |s| s.name = 'mwrap' - s.version = desc.empty? ? '2.1.0' : desc + s.version = desc.empty? ? '2.2.0' : desc s.homepage = 'https://80x24.org/mwrap/' - s.authors = ["Ruby hackers"] + s.authors = ["mwrap hackers"] s.summary = 'LD_PRELOAD malloc wrapper for Ruby' s.executables = %w(mwrap) s.files = manifest -- cgit v1.2.3-24-ge0c7 From 867b36082111f564f6e55f63cd21fa0c94df2d20 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Aug 2022 09:00:30 +0000 Subject: support --enabled-shared builds of Ruby Most GNU/Linux distros build Ruby with --enable-shared, so it makes sense to support it properly even if it's not the default favored by ruby-core. __attribute__((weak)) on a local function is not weak enough for the shared library, so we add the extra check for the function's existence to have_ec_p(), instead. Tested with ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x86_64-linux-gnu] on Debian 11 (bullseye) as well as default builds w/o --enable-shared. Note: FreeBSD 12.3 appears broken with mwrap and I no longer have older FreeBSD systems handy. --- ext/mwrap/mwrap.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 85b847b..9d90298 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -32,7 +32,6 @@ static ID id_uminus; const char *rb_source_location_cstr(int *line); /* requires 2.6.0dev */ -extern int __attribute__((weak)) ruby_thread_has_gvl_p(void); #ifdef HAVE_RUBY_RACTOR_H /* Ruby 3.0+ */ extern MWRAP_TSD void * __attribute__((weak)) ruby_current_ec; @@ -45,6 +44,7 @@ extern size_t __attribute__((weak)) rb_gc_count(void); extern VALUE __attribute__((weak)) rb_cObject; extern VALUE __attribute__((weak)) rb_eTypeError; extern VALUE __attribute__((weak)) rb_yield(VALUE); +int __attribute__((weak)) ruby_thread_has_gvl_p(void); static size_t total_bytes_inc, total_bytes_dec; @@ -64,11 +64,6 @@ enum { #define IS_HEAP_PAGE_BODY ((struct src_loc *)-1) -int __attribute__((weak)) ruby_thread_has_gvl_p(void) -{ - return 0; -} - #ifdef __FreeBSD__ void *__malloc(size_t); void __free(void *); @@ -238,8 +233,8 @@ static char *int2str(int num, char *dst, size_t * size) */ static int has_ec_p(void) { - return (ruby_thread_has_gvl_p() && ruby_current_vm_ptr && - ruby_current_ec); + return ruby_thread_has_gvl_p && ruby_thread_has_gvl_p() && + ruby_current_vm_ptr && ruby_current_ec; } struct acc { -- cgit v1.2.3-24-ge0c7 From 7524237b2de98f7e407ea227fb6ca6904153c0a5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Sep 2022 09:27:39 +0000 Subject: extconf.rb: avoid RDoc errors during gem install RDoc doesn't seem to like <<'' (blank line) as a heredoc terminator. That said, RDoc scanning extconf.rb during "gem install" is unexpected and make no sense to me. Normal `rdoc' invocations seem to ignore extconf.rb. --- ext/mwrap/extconf.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/mwrap/extconf.rb b/ext/mwrap/extconf.rb index 512ab82..1828407 100644 --- a/ext/mwrap/extconf.rb +++ b/ext/mwrap/extconf.rb @@ -11,15 +11,15 @@ have_library 'dl' have_library 'c' have_library 'execinfo' # FreeBSD -if try_link(<<'') +if try_link(< Date: Sat, 3 Sep 2022 09:27:40 +0000 Subject: add --version and --help args These may make things easier for new users, and we'll also with help text if given no args. We'll programmatically generate version based on `git describe', but fallback to a hardcoded version if outside of git. We'll also start appending `-dirty' to the version string to match git.git conventions. --- MANIFEST | 3 +++ VERSION-GEN | 36 ++++++++++++++++++++++++++++++++++++ bin/mwrap | 19 +++++++++++++++++++ lib/mwrap/.gitignore | 1 + mwrap.gemspec | 9 ++++++--- 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100755 VERSION-GEN create mode 100644 lib/mwrap/.gitignore diff --git a/MANIFEST b/MANIFEST index e6d8964..e8ace8b 100644 --- a/MANIFEST +++ b/MANIFEST @@ -5,10 +5,13 @@ COPYING MANIFEST README Rakefile +VERSION-GEN bin/mwrap ext/mwrap/extconf.rb ext/mwrap/jhash.h ext/mwrap/mwrap.c +lib/mwrap/.gitignore lib/mwrap_rack.rb mwrap.gemspec test/test_mwrap.rb +lib/mwrap/version.rb diff --git a/VERSION-GEN b/VERSION-GEN new file mode 100755 index 0000000..161a04f --- /dev/null +++ b/VERSION-GEN @@ -0,0 +1,36 @@ +#!/bin/sh +VF=lib/mwrap/version.rb +DEF_VER=v2.2.0 +VN=$(git describe HEAD 2>/dev/null) +if test $? -eq 0 +then + case "$VN" in + v[0-9]*) + set -e + git update-index -q --refresh + set +e + git diff-index --quiet HEAD -- || VN="$VN-dirty" + set -e + VN=$(echo $VN | tr '-' '.') + ;; + esac +fi +set -e + +case $VN in +'') VN="$DEF_VER" ;; +esac + +VN=$(expr "$VN" : v*'\(.*\)') +VC=unset +if test -r $VF +then + VC="$(cat $VF)" +fi + +new="module Mwrap; VERSION = '$VN'.freeze; end" +if test x"$new" != x"$VC" +then + echo "$new" >$VF +fi +echo $VN diff --git a/bin/mwrap b/bin/mwrap index 212078c..054b3a3 100755 --- a/bin/mwrap +++ b/bin/mwrap @@ -2,6 +2,25 @@ # frozen_string_literal: true # Copyright (C) mwrap hackers # License: GPL-2.0+ +help = </dev/null`.split("\n") +git_ok = $?.success? +git_manifest << 'lib/mwrap/version.rb'.freeze # generated by ./VERSION-GEN manifest = File.exist?('MANIFEST') ? File.readlines('MANIFEST').map!(&:chomp).delete_if(&:empty?) : git_manifest -if git_manifest[0] && manifest != git_manifest +if git_ok && manifest != git_manifest tmp = "MANIFEST.#$$.tmp" File.open(tmp, 'w') { |fp| fp.puts(git_manifest.join("\n")) } File.rename(tmp, 'MANIFEST') system('git add MANIFEST') end -desc = `git describe --abbrev=4 HEAD`.strip.tr('-', '.').delete_prefix('v') +version = `./VERSION-GEN`.chomp +$?.success? or abort './VERSION-GEN failed' Gem::Specification.new do |s| s.name = 'mwrap' - s.version = desc.empty? ? '2.2.0' : desc + s.version = version s.homepage = 'https://80x24.org/mwrap/' s.authors = ["mwrap hackers"] s.summary = 'LD_PRELOAD malloc wrapper for Ruby' -- cgit v1.2.3-24-ge0c7 From d505b403d5d8810bc104425d9296b358f49bddb5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Sep 2022 09:27:41 +0000 Subject: cleanup some FreeBSD-related workarounds While FreeBSD 12.3 + Ruby 3.0.4p208 remains broken with mwrap (likely due to threading bugs in Ruby) at least get the code more consistently closer to a working state. Thus we'll remember to account for PTHREAD_MUTEX_INITIALIZER requiring malloc for HeapPageBody tracking, as we do with other mutexes. For reference, the Perl5 port of mwrap seems to have no problems on FreeBSD 12.3, so it seems down to misbehavior w.r.t. pthreads usage within Ruby itself. Perl5 on FreeBSD is configured with threads support, but Perl5 doesn't spawn background threads by default like Ruby can. --- README | 2 +- ext/mwrap/mwrap.c | 93 +++++++++++++++++++++++++++++++------------------------ 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/README b/README index f387bc4..539073e 100644 --- a/README +++ b/README @@ -23,7 +23,7 @@ It does not require recompiling or rebuilding Ruby, but only supports Ruby trunk (2.6.0dev+) on a few platforms: * GNU/Linux -* FreeBSD (tested 11.1) +* FreeBSD (tested 11.1 on Ruby 2.6, currently broken with Ruby 3.x) It may work on NetBSD, OpenBSD and DragonFly BSD. diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 9d90298..1d6baec 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -99,9 +99,43 @@ union padded_mutex { /* a round-robin pool of mutexes */ #define MUTEX_NR (1 << 6) #define MUTEX_MASK (MUTEX_NR - 1) +#ifdef __FreeBSD__ +# define STATIC_MTX_INIT_OK (0) +#else /* only tested on Linux + glibc */ +# define STATIC_MTX_INIT_OK (1) +#endif static size_t mutex_i; static union padded_mutex mutexes[MUTEX_NR] = { +#if STATIC_MTX_INIT_OK [0 ... (MUTEX_NR-1)].mtx = PTHREAD_MUTEX_INITIALIZER +#endif +}; + +#define ACC_INIT(name) { .nr=0, .min=INT64_MAX, .max=-1, .m2=0, .mean=0 } +struct acc { + uint64_t nr; + int64_t min; + int64_t max; + double m2; + double mean; +}; + +/* for tracking 16K-aligned heap page bodies (protected by GVL) */ +struct { + pthread_mutex_t lock; + struct cds_list_head bodies; + struct cds_list_head freed; + + struct acc alive; + struct acc reborn; +} hpb_stats = { +#if STATIC_MTX_INIT_OK + .lock = PTHREAD_MUTEX_INITIALIZER, +#endif + .bodies = CDS_LIST_HEAD_INIT(hpb_stats.bodies), + .freed = CDS_LIST_HEAD_INIT(hpb_stats.freed), + .alive = ACC_INIT(hpb_stats.alive), + .reborn = ACC_INIT(hpb_stats.reborn) }; static pthread_mutex_t *mutex_assign(void) @@ -120,12 +154,11 @@ __attribute__((constructor)) static void resolve_malloc(void) int err; ++locating; -#ifdef __FreeBSD__ /* * PTHREAD_MUTEX_INITIALIZER on FreeBSD means lazy initialization, * which happens at pthread_mutex_lock, and that calls calloc */ - { + if (!STATIC_MTX_INIT_OK) { size_t i; for (i = 0; i < MUTEX_NR; i++) { @@ -135,22 +168,28 @@ __attribute__((constructor)) static void resolve_malloc(void) _exit(1); } } + err = pthread_mutex_init(&hpb_stats.lock, 0); + if (err) { + fprintf(stderr, "error: %s\n", strerror(err)); + _exit(1); + } /* initialize mutexes used by urcu-bp */ rcu_read_lock(); rcu_read_unlock(); +#ifndef __FreeBSD__ + } else { + if (!real_malloc) { + resolving_malloc = 1; + real_malloc = dlsym(RTLD_NEXT, "malloc"); + } + real_free = dlsym(RTLD_NEXT, "free"); + if (!real_malloc || !real_free) { + fprintf(stderr, "missing malloc/aligned_alloc/free\n" + "\t%p %p\n", real_malloc, real_free); + _exit(1); + } +#endif /* !__FreeBSD__ */ } -#else /* !FreeBSD (tested on GNU/Linux) */ - if (!real_malloc) { - resolving_malloc = 1; - real_malloc = dlsym(RTLD_NEXT, "malloc"); - } - real_free = dlsym(RTLD_NEXT, "free"); - if (!real_malloc || !real_free) { - fprintf(stderr, "missing malloc/aligned_alloc/free\n" - "\t%p %p\n", real_malloc, real_free); - _exit(1); - } -#endif /* !FreeBSD */ CMM_STORE_SHARED(totals, lfht_new()); if (!CMM_LOAD_SHARED(totals)) fprintf(stderr, "failed to allocate totals table\n"); @@ -237,32 +276,6 @@ static int has_ec_p(void) ruby_current_vm_ptr && ruby_current_ec; } -struct acc { - uint64_t nr; - int64_t min; - int64_t max; - double m2; - double mean; -}; - -#define ACC_INIT(name) { .nr=0, .min=INT64_MAX, .max=-1, .m2=0, .mean=0 } - -/* for tracking 16K-aligned heap page bodies (protected by GVL) */ -struct { - pthread_mutex_t lock; - struct cds_list_head bodies; - struct cds_list_head freed; - - struct acc alive; - struct acc reborn; -} hpb_stats = { - .lock = PTHREAD_MUTEX_INITIALIZER, - .bodies = CDS_LIST_HEAD_INIT(hpb_stats.bodies), - .freed = CDS_LIST_HEAD_INIT(hpb_stats.freed), - .alive = ACC_INIT(hpb_stats.alive), - .reborn = ACC_INIT(hpb_stats.reborn) -}; - /* allocated via real_malloc/real_free */ struct src_loc { pthread_mutex_t *mtx; -- cgit v1.2.3-24-ge0c7 From e6f1dbf1c169b8bd7f2fd3e24d1618eb0ddb2666 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Sep 2022 09:54:40 +0000 Subject: mwrap 2.3.0 This release now supports --enable-shared builds of Ruby which is the common setting for GNU/Linux distros. FreeBSD 12.3 support remains broken, but the code is somewhat revised so it can be more easily fixed and maintained in the future. `mwrap --help' and and `mwrap --version' are now supported, and an RDoc error which only happens during `gem install' is fixed. * support --enabled-shared builds of Ruby * extconf.rb: avoid RDoc errors during gem install * add --version and --help args * cleanup some FreeBSD-related workarounds --- VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION-GEN b/VERSION-GEN index 161a04f..ae66e94 100755 --- a/VERSION-GEN +++ b/VERSION-GEN @@ -1,6 +1,6 @@ #!/bin/sh VF=lib/mwrap/version.rb -DEF_VER=v2.2.0 +DEF_VER=v2.3.0 VN=$(git describe HEAD 2>/dev/null) if test $? -eq 0 then -- cgit v1.2.3-24-ge0c7 From fd15a49eb477b345e97ca3bbd6cde135cd0d6583 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Sep 2022 10:50:53 +0000 Subject: Ractor compatibility We can no longer depend on having GVL/GIL when we have a Ruby execution context, so so make `kbuf' thread-specific to avoid data corruption. This was ported from the Perl5 version, since Perl5 has had MVM/Ractor-like abilities with native threads for decades, despite the Perl5 ecosystem largely avoids and discourages threads. --- ext/mwrap/mwrap.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 1d6baec..90f560e 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -308,7 +308,9 @@ struct alloc_hdr { size_t size; }; -static char kbuf[PATH_MAX + INT2STR_MAX + sizeof(struct alloc_hdr) + 2]; +static MWRAP_TSD char kbuf[ + PATH_MAX + INT2STR_MAX + sizeof(struct alloc_hdr) + 2 +]; static struct alloc_hdr *ptr2hdr(void *p) { -- cgit v1.2.3-24-ge0c7 From 13ecb7e5cf4ee769801e53d9df87141c6730e825 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Sep 2022 10:58:20 +0000 Subject: paranoid safety fix to clamp pathnames to PATH_MAX While I doubt Ruby (nor Perl) would store pathnames longer than PATH_MAX by default, it's possible `eval' users to specify whatever path (and line number) they wish to use. Likely was the case with `# line $FILE' directives in Perl5 which prompted this clamping. --- ext/mwrap/mwrap.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 90f560e..08761d6 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -470,6 +470,8 @@ static struct src_loc *update_stats_rcu_lock(size_t size, uintptr_t caller) /* avoid vsnprintf or anything which could call malloc here: */ len = strlen(ptr); + if (len > PATH_MAX) + len = PATH_MAX; k = (void *)kbuf; k->total = size; dst = mempcpy(k->k, ptr, len); -- cgit v1.2.3-24-ge0c7 From a03b72e85011b71e031447b9c8c917e6f2c08c81 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 7 Jan 2023 21:51:14 +0000 Subject: drop heap page support for Ruby <= 3.0 Ruby 3.1 uses mmap, nowadays, and I don't think it's worth the effort to suport it since mmap and munmap don't require the symmetry *memalign + free do. --- ext/mwrap/extconf.rb | 7 -- ext/mwrap/mwrap.c | 327 ++------------------------------------------------- lib/mwrap_rack.rb | 51 -------- test/test_mwrap.rb | 38 ------ 4 files changed, 12 insertions(+), 411 deletions(-) diff --git a/ext/mwrap/extconf.rb b/ext/mwrap/extconf.rb index 1828407..e8d3cc6 100644 --- a/ext/mwrap/extconf.rb +++ b/ext/mwrap/extconf.rb @@ -25,11 +25,4 @@ else abort 'missing __builtin_add_overflow' end -begin - if n = GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE] - $defs << "-DHEAP_PAGE_SIZE=#{n}" - end -rescue NameError -end - create_makefile 'mwrap' diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 08761d6..6875486 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -51,19 +51,6 @@ static size_t total_bytes_inc, total_bytes_dec; /* true for glibc/dlmalloc/ptmalloc, not sure about jemalloc */ #define ASSUMED_MALLOC_ALIGNMENT (sizeof(void *) * 2) -/* match values in Ruby gc.c */ -#define HEAP_PAGE_ALIGN_LOG 14 -enum { - HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG) -#ifndef HEAP_PAGE_SIZE /* Ruby 2.6-2.7 only */ - , - REQUIRED_SIZE_BY_MALLOC = (sizeof(size_t) * 5), - HEAP_PAGE_SIZE = (HEAP_PAGE_ALIGN - REQUIRED_SIZE_BY_MALLOC) -#endif -}; - -#define IS_HEAP_PAGE_BODY ((struct src_loc *)-1) - #ifdef __FreeBSD__ void *__malloc(size_t); void __free(void *); @@ -111,33 +98,6 @@ static union padded_mutex mutexes[MUTEX_NR] = { #endif }; -#define ACC_INIT(name) { .nr=0, .min=INT64_MAX, .max=-1, .m2=0, .mean=0 } -struct acc { - uint64_t nr; - int64_t min; - int64_t max; - double m2; - double mean; -}; - -/* for tracking 16K-aligned heap page bodies (protected by GVL) */ -struct { - pthread_mutex_t lock; - struct cds_list_head bodies; - struct cds_list_head freed; - - struct acc alive; - struct acc reborn; -} hpb_stats = { -#if STATIC_MTX_INIT_OK - .lock = PTHREAD_MUTEX_INITIALIZER, -#endif - .bodies = CDS_LIST_HEAD_INIT(hpb_stats.bodies), - .freed = CDS_LIST_HEAD_INIT(hpb_stats.freed), - .alive = ACC_INIT(hpb_stats.alive), - .reborn = ACC_INIT(hpb_stats.reborn) -}; - static pthread_mutex_t *mutex_assign(void) { return &mutexes[uatomic_add_return(&mutex_i, 1) & MUTEX_MASK].mtx; @@ -168,11 +128,6 @@ __attribute__((constructor)) static void resolve_malloc(void) _exit(1); } } - err = pthread_mutex_init(&hpb_stats.lock, 0); - if (err) { - fprintf(stderr, "error: %s\n", strerror(err)); - _exit(1); - } /* initialize mutexes used by urcu-bp */ rcu_read_lock(); rcu_read_unlock(); @@ -300,9 +255,6 @@ struct alloc_hdr { struct src_loc *loc; } live; struct rcu_head dead; - struct { - size_t at; /* rb_gc_count() */ - } hpb_freed; } as; void *real; /* what to call real_free on */ size_t size; @@ -344,64 +296,6 @@ static int loc_eq(struct cds_lfht_node *node, const void *key) memcmp(k->k, existing->k, loc_size(k)) == 0); } -/* note: not atomic */ -static void -acc_add(struct acc *acc, size_t val) -{ - double delta = val - acc->mean; - uint64_t nr = ++acc->nr; - - /* just don't divide-by-zero if we ever hit this (unlikely :P) */ - if (nr) - acc->mean += delta / nr; - - acc->m2 += delta * (val - acc->mean); - if ((int64_t)val < acc->min) - acc->min = (int64_t)val; - if ((int64_t)val > acc->max) - acc->max = (int64_t)val; -} - -#if SIZEOF_LONG == 8 -# define INT64toNUM(x) LONG2NUM((long)x) -#elif defined(HAVE_LONG_LONG) && SIZEOF_LONG_LONG == 8 -# define INT64toNUM(x) LL2NUM((LONG_LONG)x) -#endif - -static VALUE -acc_max(const struct acc *acc) -{ - return INT64toNUM(acc->max); -} - -static VALUE -acc_min(const struct acc *acc) -{ - return acc->min == INT64_MAX ? INT2FIX(-1) : INT64toNUM(acc->min); -} - -static VALUE -acc_mean(const struct acc *acc) -{ - return DBL2NUM(acc->nr ? acc->mean : HUGE_VAL); -} - -static double -acc_stddev_dbl(const struct acc *acc) -{ - if (acc->nr > 1) { - double variance = acc->m2 / (acc->nr - 1); - return sqrt(variance); - } - return 0.0; -} - -static VALUE -acc_stddev(const struct acc *acc) -{ - return DBL2NUM(acc_stddev_dbl(acc)); -} - static struct src_loc *totals_add_rcu(const struct src_loc *k) { struct cds_lfht_iter iter; @@ -519,7 +413,7 @@ void free(void *p) struct src_loc *l = h->as.live.loc; if (!real_free) return; /* oh well, leak a little */ - if (l && l != IS_HEAP_PAGE_BODY) { + if (l) { size_t age = generation - h->as.live.gen; uatomic_add(&total_bytes_dec, h->size); @@ -534,19 +428,6 @@ void free(void *p) mutex_unlock(l->mtx); call_rcu(&h->as.dead, free_hdr_rcu); - } else if (l == IS_HEAP_PAGE_BODY) { - size_t gen = generation; - size_t age = gen - h->as.live.gen; - - h->as.hpb_freed.at = gen; - - mutex_lock(&hpb_stats.lock); - acc_add(&hpb_stats.alive, age); - - /* hpb_stats.bodies => hpb_stats.freed */ - cds_list_move(&h->anode, &hpb_stats.freed); - - mutex_unlock(&hpb_stats.lock); } else { real_free(h->real); } @@ -614,65 +495,18 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller) return ENOMEM; - if (alignment == HEAP_PAGE_ALIGN && size == HEAP_PAGE_SIZE) { - if (has_ec_p()) generation = rb_gc_count(); - l = IS_HEAP_PAGE_BODY; - } else { - l = update_stats_rcu_lock(size, caller); - } + l = update_stats_rcu_lock(size, caller); - if (l == IS_HEAP_PAGE_BODY) { - void *p; - size_t gen = generation; - - mutex_lock(&hpb_stats.lock); - - /* reuse existing entry */ - if (!cds_list_empty(&hpb_stats.freed)) { - size_t deathspan; - - h = cds_list_first_entry(&hpb_stats.freed, - struct alloc_hdr, anode); - /* hpb_stats.freed => hpb_stats.bodies */ - cds_list_move(&h->anode, &hpb_stats.bodies); - assert(h->size == size); - assert(h->real); - real = h->real; - p = hdr2ptr(h); - assert(ptr_is_aligned(p, alignment)); - - deathspan = gen - h->as.hpb_freed.at; - acc_add(&hpb_stats.reborn, deathspan); - } - else { - real = real_malloc(asize); - if (!real) return ENOMEM; - - p = hdr2ptr(real); - if (!ptr_is_aligned(p, alignment)) - p = ptr_align(p, alignment); - h = ptr2hdr(p); - h->size = size; - h->real = real; - cds_list_add(&h->anode, &hpb_stats.bodies); - } - mutex_unlock(&hpb_stats.lock); - h->as.live.loc = l; - h->as.live.gen = gen; + real = real_malloc(asize); + if (real) { + void *p = hdr2ptr(real); + if (!ptr_is_aligned(p, alignment)) + p = ptr_align(p, alignment); + h = ptr2hdr(p); + alloc_insert_rcu(l, h, size, real); *pp = p; } - else { - real = real_malloc(asize); - if (real) { - void *p = hdr2ptr(real); - if (!ptr_is_aligned(p, alignment)) - p = ptr_align(p, alignment); - h = ptr2hdr(p); - alloc_insert_rcu(l, h, size, real); - *pp = p; - } - update_stats_rcu_unlock(l); - } + update_stats_rcu_unlock(l); return real ? 0 : ENOMEM; } @@ -1243,73 +1077,6 @@ static VALUE total_dec(VALUE mod) return SIZET2NUM(total_bytes_dec); } -static VALUE hpb_each_yield(VALUE ignore) -{ - struct alloc_hdr *h, *next; - - cds_list_for_each_entry_safe(h, next, &hpb_stats.bodies, anode) { - VALUE v[2]; /* [ generation, address ] */ - void *addr = hdr2ptr(h); - assert(ptr_is_aligned(addr, HEAP_PAGE_ALIGN)); - v[0] = LONG2NUM((long)addr); - v[1] = SIZET2NUM(h->as.live.gen); - rb_yield_values2(2, v); - } - return Qnil; -} - -/* - * call-seq: - * - * Mwrap::HeapPageBody.each { |gen, addr| } -> Integer - * - * Yields the generation (GC.count) the heap page body was created - * and address of the heap page body as an Integer. Returns the - * number of allocated pages as an Integer. This return value should - * match the result of GC.stat(:heap_allocated_pages) - */ -static VALUE hpb_each(VALUE mod) -{ - ++locating; - return rb_ensure(hpb_each_yield, Qfalse, reset_locating, 0); -} - -/* - * call-seq: - * - * Mwrap::HeapPageBody.stat -> Hash - * Mwrap::HeapPageBody.stat(hash) -> hash - * - * The maximum lifespan of a heap page body in the Ruby VM. - * This may be Infinity if no heap page bodies were ever freed. - */ -static VALUE hpb_stat(int argc, VALUE *argv, VALUE hpb) -{ - VALUE h; - - rb_scan_args(argc, argv, "01", &h); - if (NIL_P(h)) - h = rb_hash_new(); - else if (!RB_TYPE_P(h, T_HASH)) - rb_raise(rb_eTypeError, "not a hash %+"PRIsVALUE, h); - - ++locating; -#define S(x) ID2SYM(rb_intern(#x)) - rb_hash_aset(h, S(lifespan_max), acc_max(&hpb_stats.alive)); - rb_hash_aset(h, S(lifespan_min), acc_min(&hpb_stats.alive)); - rb_hash_aset(h, S(lifespan_mean), acc_mean(&hpb_stats.alive)); - rb_hash_aset(h, S(lifespan_stddev), acc_stddev(&hpb_stats.alive)); - rb_hash_aset(h, S(deathspan_max), acc_max(&hpb_stats.reborn)); - rb_hash_aset(h, S(deathspan_min), acc_min(&hpb_stats.reborn)); - rb_hash_aset(h, S(deathspan_mean), acc_mean(&hpb_stats.reborn)); - rb_hash_aset(h, S(deathspan_stddev), acc_stddev(&hpb_stats.reborn)); - rb_hash_aset(h, S(resurrects), SIZET2NUM(hpb_stats.reborn.nr)); -#undef S - --locating; - - return h; -} - /* * Document-module: Mwrap * @@ -1328,19 +1095,13 @@ static VALUE hpb_stat(int argc, VALUE *argv, VALUE hpb) * * dump_fd: a writable FD to dump to * * dump_path: a path to dump to, the file is opened in O_APPEND mode * * dump_min: the minimum allocation size (total) to dump - * * dump_heap: mask of heap_page_body statistics to dump * * If both `dump_fd' and `dump_path' are specified, dump_path takes * precedence. - * - * dump_heap bitmask - * * 0x01 - summary stats (same info as HeapPageBody.stat) - * * 0x02 - all live heaps (similar to HeapPageBody.each) - * * 0x04 - skip non-heap_page_body-related output */ void Init_mwrap(void) { - VALUE mod, hpb; + VALUE mod; ++locating; mod = rb_define_module("Mwrap"); @@ -1372,67 +1133,9 @@ void Init_mwrap(void) rb_define_method(cSrcLoc, "max_lifespan", src_loc_max_lifespan, 0); rb_define_method(cSrcLoc, "name", src_loc_name, 0); - /* - * Information about "struct heap_page_body" allocations from - * Ruby gc.c. This can be useful for tracking fragmentation - * from posix_memalign(3) use in mainline Ruby: - * - * https://sourceware.org/bugzilla/show_bug.cgi?id=14581 - * - * These statistics are never reset by Mwrap.reset or - * any other method. They only make sense in the context - * of an entire program lifetime. - */ - hpb = rb_define_class_under(mod, "HeapPageBody", rb_cObject); - rb_define_singleton_method(hpb, "stat", hpb_stat, -1); - rb_define_singleton_method(hpb, "each", hpb_each, 0); - --locating; } -enum { - DUMP_HPB_STATS = 0x1, - DUMP_HPB_EACH = 0x2, - DUMP_HPB_EXCL = 0x4, -}; - -static void dump_hpb(FILE *fp, unsigned flags) -{ - if (flags & DUMP_HPB_STATS) { - fprintf(fp, - "lifespan_max: %"PRId64"\n" - "lifespan_min:%s%"PRId64"\n" - "lifespan_mean: %0.3f\n" - "lifespan_stddev: %0.3f\n" - "deathspan_max: %"PRId64"\n" - "deathspan_min:%s%"PRId64"\n" - "deathspan_mean: %0.3f\n" - "deathspan_stddev: %0.3f\n" - "gc_count: %zu\n", - hpb_stats.alive.max, - hpb_stats.alive.min == INT64_MAX ? " -" : " ", - hpb_stats.alive.min, - hpb_stats.alive.mean, - acc_stddev_dbl(&hpb_stats.alive), - hpb_stats.reborn.max, - hpb_stats.reborn.min == INT64_MAX ? " -" : " ", - hpb_stats.reborn.min, - hpb_stats.reborn.mean, - acc_stddev_dbl(&hpb_stats.reborn), - /* n.b.: unsafe to call rb_gc_count() in destructor */ - generation); - } - if (flags & DUMP_HPB_EACH) { - struct alloc_hdr *h; - - cds_list_for_each_entry(h, &hpb_stats.bodies, anode) { - void *addr = hdr2ptr(h); - - fprintf(fp, "%p\t%zu\n", addr, h->as.live.gen); - } - } -} - /* rb_cloexec_open isn't usable by non-Ruby processes */ #ifndef O_CLOEXEC # define O_CLOEXEC 0 @@ -1446,7 +1149,6 @@ static void mwrap_dump_destructor(void) struct dump_arg a = { .min = 0 }; size_t i; int dump_fd; - unsigned dump_heap = 0; char *dump_path; char *s; @@ -1478,9 +1180,6 @@ static void mwrap_dump_destructor(void) if ((s = strstr(opt, "dump_min:"))) sscanf(s, "dump_min:%zu", &a.min); - if ((s = strstr(opt, "dump_heap:"))) - sscanf(s, "dump_heap:%u", &dump_heap); - switch (dump_fd) { case 0: goto out; case 1: a.fp = stdout; break; @@ -1500,9 +1199,7 @@ static void mwrap_dump_destructor(void) } /* we'll leak some memory here, but this is a destructor */ } - if ((dump_heap & DUMP_HPB_EXCL) == 0) - dump_to_file(&a); - dump_hpb(a.fp, dump_heap); + dump_to_file(&a); out: --locating; } diff --git a/lib/mwrap_rack.rb b/lib/mwrap_rack.rb index 53380b9..c777a78 100644 --- a/lib/mwrap_rack.rb +++ b/lib/mwrap_rack.rb @@ -89,54 +89,6 @@ class MwrapRack end end - class HeapPages # :nodoc: - include HtmlResponse - HEADER = 'addressgeneration' - - def hpb_rows - Mwrap::HeapPageBody.stat(stat = Thread.current[:mwrap_hpb_stat] ||= {}) - %i(lifespan_max lifespan_min lifespan_mean lifespan_stddev - deathspan_max deathspan_min deathspan_mean deathspan_stddev - resurrects - ).map! do |k| - "#{k}#{stat[k]}\n" - end.join - end - - def gc_stat_rows - GC.stat(stat = Thread.current[:mwrap_gc_stat] ||= {}) - %i(count heap_allocated_pages heap_eden_pages heap_tomb_pages - total_allocated_pages total_freed_pages).map do |k| - "GC.stat(:#{k})#{stat[k]}\n" - end.join - end - - GC_STAT_URL = 'https://docs.ruby-lang.org/en/trunk/GC.html#method-c-stat' - GC_STAT_HELP = <<~EOM -

Non-Infinity lifespans can indicate fragmentation. -

See #{GC_STAT_URL} for info on GC.stat values. - EOM - - def each - Mwrap.quiet do - yield("heap pages" \ - "

heap pages

" \ - "\n" \ - "#{hpb_rows}" \ - "#{gc_stat_rows}" \ - "
statvalue
\n" \ - "#{GC_STAT_HELP}" \ - "#{HEADER}") - Mwrap::HeapPageBody.each do |addr, generation| - addr = -sprintf('0x%x', addr) - yield(-"\n") - end - yield "
#{addr}#{generation}
\n" - end - end - end - def r404 # :nodoc: [404,{'Content-Type'=>'text/plain'},["Not found\n"]] end @@ -152,15 +104,12 @@ class MwrapRack loc = -CGI.unescape($1) loc = Mwrap[loc] or return r404 EachAt.new(loc).response - when '/heap_pages' - HeapPages.new.response when '/' n = 2000 u = 'https://80x24.org/mwrap/README.html' b = -('Mwrap demo' \ "

allocations >#{n} bytes" \ "

#{u}" \ - "

heap pages" \ "\n") [ 200, {'Content-Type'=>'text/html','Content-Length'=>-b.size.to_s},[b]] else diff --git a/test/test_mwrap.rb b/test/test_mwrap.rb index eaa65cb..6522167 100644 --- a/test/test_mwrap.rb +++ b/test/test_mwrap.rb @@ -59,13 +59,6 @@ class TestMwrap < Test::Unit::TestCase res = system(env, *cmd) assert res, $?.inspect assert_match(/\b1\d{4}\s+[1-9]\d*\s+-e:1$/, tmp.read) - - tmp.rewind - tmp.truncate(0) - env['MWRAP'] = "dump_path:#{tmp.path},dump_heap:5" - res = system(env, *cmd) - assert res, $?.inspect - assert_match %r{lifespan_stddev}, tmp.read end end @@ -295,35 +288,4 @@ class TestMwrap < Test::Unit::TestCase abort 'freed more than allocated' end; end - - def test_heap_page_body - assert_separately(+"#{<<~"begin;"}\n#{<<~'end;'}") - begin; - require 'mwrap' - require 'rubygems' # use up some memory - ap = GC.stat(:heap_allocated_pages) - h = {} - nr = 0 - Mwrap::HeapPageBody.each do |addr, gen| - nr += 1 - gen <= GC.count && gen >= 0 or abort "bad generation: #{gen}" - (0 == (addr & 16383)) or abort "addr not aligned: #{'%x' % addr}" - end - if RUBY_VERSION.to_f < 3.1 # 3.1+ uses mmap on platforms we care about - nr == ap or abort "HeapPageBody.each missed page #{nr} != #{ap}" - end - 10.times { (1..20000).to_a.map(&:to_s) } - 3.times { GC.start } - Mwrap::HeapPageBody.stat(h) - Integer === h[:lifespan_max] or abort 'lifespan_max not recorded' - Integer === h[:lifespan_min] or abort 'lifespan_min not recorded' - Float === h[:lifespan_mean] or abort 'lifespan_mean not recorded' - 3.times { GC.start } - 10.times { (1..20000).to_a.map(&:to_s) } - Mwrap::HeapPageBody.stat(h) - h[:deathspan_min] <= h[:deathspan_max] or - abort 'wrong min/max deathtime' - Float === h[:deathspan_mean] or abort 'deathspan_mean not recorded' - end; - end end -- cgit v1.2.3-24-ge0c7 From 40893ed4ddc5d92cce36ee951e9c7ae67b96f41a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 7 Jan 2023 21:47:12 +0000 Subject: mwrap_rack: lowercase response headers for Rack 3 Rack 3 requires lowercase headers, and they work with any Rack <=2.x version. --- lib/mwrap_rack.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/mwrap_rack.rb b/lib/mwrap_rack.rb index c777a78..1bd00ac 100644 --- a/lib/mwrap_rack.rb +++ b/lib/mwrap_rack.rb @@ -22,10 +22,10 @@ class MwrapRack module HtmlResponse # :nodoc: def response [ 200, { - 'Expires' => 'Fri, 01 Jan 1980 00:00:00 GMT', - 'Pragma' => 'no-cache', - 'Cache-Control' => 'no-cache, max-age=0, must-revalidate', - 'Content-Type' => 'text/html; charset=UTF-8', + 'expires' => 'Fri, 01 Jan 1980 00:00:00 GMT', + 'pragma' => 'no-cache', + 'cache-control' => 'no-cache, max-age=0, must-revalidate', + 'content-type' => 'text/html; charset=UTF-8', }, self ] end end @@ -90,7 +90,7 @@ class MwrapRack end def r404 # :nodoc: - [404,{'Content-Type'=>'text/plain'},["Not found\n"]] + [404,{'content-type'=>'text/plain'},["Not found\n"]] end # The standard Rack application endpoint for MwrapRack @@ -111,7 +111,7 @@ class MwrapRack "

allocations >#{n} bytes" \ "

#{u}" \ "\n") - [ 200, {'Content-Type'=>'text/html','Content-Length'=>-b.size.to_s},[b]] + [ 200, {'content-type'=>'text/html','content-length'=>-b.size.to_s},[b]] else r404 end -- cgit v1.2.3-24-ge0c7 From 2c25edb01139365f4754985c1e3494765dd1e5a7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 7 Jan 2023 20:13:13 +0000 Subject: undefine Mwrap::SourceLocation.allocate This quiets `undefining the allocator of T_DATA class Mwrap::SourceLocation' warnings. --- ext/mwrap/mwrap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 6875486..160007f 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -1115,6 +1115,7 @@ void Init_mwrap(void) * This class is only available since mwrap 2.0.0+. */ cSrcLoc = rb_define_class_under(mod, "SourceLocation", rb_cObject); + rb_undef_alloc_func(cSrcLoc); rb_define_singleton_method(mod, "dump", mwrap_dump, -1); rb_define_singleton_method(mod, "reset", mwrap_reset, 0); rb_define_singleton_method(mod, "clear", mwrap_clear, 0); -- cgit v1.2.3-24-ge0c7