living_dead 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +108 -0
- data/Rakefile +17 -0
- data/changelog.md +5 -0
- data/ext/living_dead/extconf.rb +2 -0
- data/ext/living_dead/living_dead.c +217 -0
- data/lib/living_dead.rb +96 -0
- data/lib/living_dead/version.rb +3 -0
- data/living_dead.gemspec +28 -0
- data/scratch.rb +20 -0
- data/spec/fixtures/singleton_class/in_class.rb +24 -0
- data/spec/fixtures/singleton_class/in_proc.rb +23 -0
- data/spec/fixtures/singleton_class/method_in_proc.rb +21 -0
- data/spec/fixtures/singleton_class/retained.rb +20 -0
- data/spec/fixtures/singleton_class/simple.rb +21 -0
- data/spec/fixtures/singleton_class_instance_eval/in_class.rb +24 -0
- data/spec/fixtures/singleton_class_instance_eval/in_proc.rb +24 -0
- data/spec/fixtures/singleton_class_instance_eval/method_in_proc.rb +22 -0
- data/spec/fixtures/singleton_class_instance_eval/retained.rb +21 -0
- data/spec/fixtures/singleton_class_instance_eval/simple.rb +22 -0
- data/spec/fixtures/string/not_retained.rb +19 -0
- data/spec/fixtures/string/retained.rb +19 -0
- data/spec/fixtures/string/string_in_class.rb +22 -0
- data/spec/fixtures/string/string_in_proc.rb +21 -0
- data/spec/fixtures/string/string_method_in_proc.rb +20 -0
- data/spec/fixtures/times_map/in_class.rb +24 -0
- data/spec/fixtures/times_map/in_proc.rb +25 -0
- data/spec/fixtures/times_map/method_in_proc.rb +22 -0
- data/spec/fixtures/times_map/retained.rb +22 -0
- data/spec/fixtures/times_map/simple.rb +22 -0
- data/spec/living_dead/singleton_class_instance_eval_spec.rb +30 -0
- data/spec/living_dead/singleton_class_spec.rb +30 -0
- data/spec/living_dead/string_spec.rb +24 -0
- data/spec/living_dead/times_map_spec.rb +30 -0
- data/spec/living_dead_spec.rb +24 -0
- data/spec/spec_helper.rb +28 -0
- metadata +168 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6409ddbc4318a1d8753890c7f29344587e66fe9f
|
4
|
+
data.tar.gz: 26d81012fe0155e3f8fe0c359f4cbae0d9190d98
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 28f80d02748e796776325a4fc6e8922a0442480ae9ec9395ce66c040ca8ee2824e45e9e3325032683ac6e640dff5be58bd8eabe6171a265be1ad337d5c352a4f
|
7
|
+
data.tar.gz: c10210bc2c3d0ab0fb3e5175810824b4423fcfdff639c3ef871c3c66d45c0244076e1d1b086945d7c062072aa61e12369792338183d2bc16b31232072e6ec5c0
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 Richard Schneeman
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# LivingDead
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/schneems/living_dead.svg?branch=master)](https://travis-ci.org/schneems/living_dead)
|
4
|
+
|
5
|
+
This module allows to see if an object is retained "still alive" or if it is freed "dead".
|
6
|
+
|
7
|
+
![Dancing Zombies](https://www.dropbox.com/s/lshgqzek77107mh/zombies.gif?dl=1)
|
8
|
+
|
9
|
+
## Problems
|
10
|
+
|
11
|
+
There be dragons see `LivingDead.gc_start` to see some of the hacks we have to do for who knows why to get this to work.
|
12
|
+
|
13
|
+
## Install
|
14
|
+
|
15
|
+
In your Gemfile add:
|
16
|
+
|
17
|
+
```
|
18
|
+
gem 'living_dead'
|
19
|
+
```
|
20
|
+
|
21
|
+
Then run
|
22
|
+
|
23
|
+
```
|
24
|
+
$ bundle install
|
25
|
+
```
|
26
|
+
|
27
|
+
## How it works
|
28
|
+
|
29
|
+
Before you use this you should understand how it works. This gem is a c-extension. It hooks into the Ruby tracepoint API and registeres a hook for the `RUBY_INTERNAL_EVENT_FREEOBJ` event. This
|
30
|
+
event gets called when an object is freed (i.e. it is not retained and garbage collection has been called).
|
31
|
+
|
32
|
+
When you call `LivingDead.trace(obj)` we store the `object_id` of the thing you are "tracing" in a hash. Then inside of our c-extension hook we listen for when an object is freed. When this happens we check to see if that object's `object_id` matches one in our hash. If it does we mark it down in a seperate "freed" hash.
|
33
|
+
|
34
|
+
We don't retain the objects you are tracing but we do keep a copy of the `object_id`, we can then use this same number to check the freed hash to see if it was recorded as being freed.
|
35
|
+
|
36
|
+
> WARNING: Seriously, see `LivingDead.gc_start`. This library isn't bullet proof.
|
37
|
+
|
38
|
+
## Quick Start
|
39
|
+
|
40
|
+
Require the library and use `LivingDead.trace` to "trace" an object. Later use `LivingDead.traced_objects` to iterate through "tracers" of each object.
|
41
|
+
|
42
|
+
Here is an example of tracing an object that is not retained:
|
43
|
+
|
44
|
+
```
|
45
|
+
require 'living_dead'
|
46
|
+
|
47
|
+
def run
|
48
|
+
obj = Object.new
|
49
|
+
LivingDead.trace(obj)
|
50
|
+
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
|
54
|
+
run
|
55
|
+
|
56
|
+
puts LivingDead.traced_objects.select {|tracer| tracer.retained? }.count
|
57
|
+
# => 0
|
58
|
+
```
|
59
|
+
|
60
|
+
> Note: Calling `LivingDead.traced_objects` auto calls `GC.start`, you don't need to do it manually. However you should look at the implementation of `LivingDead.gc_start` to understand the depth of hacks you're playing with.
|
61
|
+
|
62
|
+
Here is an example of tracing an object that IS retained:
|
63
|
+
|
64
|
+
```
|
65
|
+
require 'living_dead'
|
66
|
+
|
67
|
+
def run
|
68
|
+
obj = Object.new
|
69
|
+
LivingDead.trace(obj)
|
70
|
+
|
71
|
+
return obj
|
72
|
+
end
|
73
|
+
|
74
|
+
@retained_here = run
|
75
|
+
|
76
|
+
puts LivingDead.traced_objects.select {|tracer| tracer.retained? }.count
|
77
|
+
# => 1
|
78
|
+
```
|
79
|
+
|
80
|
+
You can get more ways of interacting with a tracer by looking at `LivingDead::ObjectTrace`.
|
81
|
+
|
82
|
+
## Development
|
83
|
+
|
84
|
+
Compile the code:
|
85
|
+
|
86
|
+
```
|
87
|
+
$ rake compile
|
88
|
+
```
|
89
|
+
|
90
|
+
Run the tests:
|
91
|
+
|
92
|
+
```
|
93
|
+
$ rake spec
|
94
|
+
```
|
95
|
+
|
96
|
+
or "why not both":
|
97
|
+
|
98
|
+
```
|
99
|
+
$ rake compile spec
|
100
|
+
```
|
101
|
+
|
102
|
+
## License
|
103
|
+
|
104
|
+
MIT
|
105
|
+
|
106
|
+
Copyright Richard Schneeman 2016
|
107
|
+
|
108
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/extensiontask"
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
spec = Gem::Specification.load('living_dead.gemspec')
|
6
|
+
|
7
|
+
Rake::ExtensionTask.new("living_dead", spec){|ext|
|
8
|
+
ext.lib_dir = "lib/living_dead"
|
9
|
+
}
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new('spec' => 'compile')
|
12
|
+
|
13
|
+
task default: :spec
|
14
|
+
|
15
|
+
task :run => 'compile' do
|
16
|
+
ruby %q{-I ./lib test.rb}
|
17
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
#include "ruby/ruby.h"
|
2
|
+
#include "ruby/debug.h"
|
3
|
+
#include <assert.h>
|
4
|
+
|
5
|
+
size_t rb_obj_memsize_of(VALUE obj); /* in gc.c */
|
6
|
+
|
7
|
+
static VALUE rb_mLivingDead;
|
8
|
+
|
9
|
+
#define MAX_KEY_DATA 4
|
10
|
+
|
11
|
+
#define KEY_PATH (1<<1)
|
12
|
+
#define KEY_LINE (1<<2)
|
13
|
+
#define KEY_TYPE (1<<3)
|
14
|
+
#define KEY_CLASS (1<<4)
|
15
|
+
|
16
|
+
#define MAX_VAL_DATA 6
|
17
|
+
|
18
|
+
#define VAL_COUNT (1<<1)
|
19
|
+
#define VAL_OLDCOUNT (1<<2)
|
20
|
+
#define VAL_TOTAL_AGE (1<<3)
|
21
|
+
#define VAL_MIN_AGE (1<<4)
|
22
|
+
#define VAL_MAX_AGE (1<<5)
|
23
|
+
#define VAL_MEMSIZE (1<<6)
|
24
|
+
|
25
|
+
struct traceobj_arg {
|
26
|
+
int running;
|
27
|
+
int keys, vals;
|
28
|
+
st_table *object_table; /* obj (VALUE) -> allocation_info */
|
29
|
+
st_table *str_table; /* cstr -> refcount */
|
30
|
+
|
31
|
+
st_table *aggregate_table; /* user defined key -> [count, total_age, max_age, min_age] */
|
32
|
+
struct allocation_info *freed_allocation_info;
|
33
|
+
|
34
|
+
/* */
|
35
|
+
size_t **lifetime_table;
|
36
|
+
size_t allocated_count_table[T_MASK];
|
37
|
+
size_t freed_count_table[T_MASK];
|
38
|
+
};
|
39
|
+
|
40
|
+
struct memcmp_key_data {
|
41
|
+
int n;
|
42
|
+
st_data_t data[MAX_KEY_DATA];
|
43
|
+
};
|
44
|
+
|
45
|
+
static int
|
46
|
+
memcmp_hash_compare(st_data_t a, st_data_t b)
|
47
|
+
{
|
48
|
+
struct memcmp_key_data *k1 = (struct memcmp_key_data *)a;
|
49
|
+
struct memcmp_key_data *k2 = (struct memcmp_key_data *)b;
|
50
|
+
return memcmp(&k1->data[0], &k2->data[0], k1->n * sizeof(st_data_t));
|
51
|
+
}
|
52
|
+
|
53
|
+
static st_index_t
|
54
|
+
memcmp_hash_hash(st_data_t a)
|
55
|
+
{
|
56
|
+
struct memcmp_key_data *k = (struct memcmp_key_data *)a;
|
57
|
+
return rb_memhash(k->data, sizeof(st_data_t) * k->n);
|
58
|
+
}
|
59
|
+
|
60
|
+
static const struct st_hash_type memcmp_hash_type = {
|
61
|
+
memcmp_hash_compare, memcmp_hash_hash
|
62
|
+
};
|
63
|
+
|
64
|
+
static struct traceobj_arg *tmp_trace_arg;
|
65
|
+
|
66
|
+
/*
|
67
|
+
* I honestly coppied this from https://github.com/ko1/allocation_tracer
|
68
|
+
* We don't need all this per-say. However it's not hurting anything by being here
|
69
|
+
* at the moment. Same goes for the majority of code above this point.
|
70
|
+
*/
|
71
|
+
static struct traceobj_arg *
|
72
|
+
get_traceobj_arg(void)
|
73
|
+
{
|
74
|
+
if (tmp_trace_arg == 0) {
|
75
|
+
tmp_trace_arg = ALLOC_N(struct traceobj_arg, 1);
|
76
|
+
MEMZERO(tmp_trace_arg, struct traceobj_arg, 1);
|
77
|
+
tmp_trace_arg->running = 0;
|
78
|
+
tmp_trace_arg->keys = 0;
|
79
|
+
tmp_trace_arg->vals = VAL_COUNT | VAL_OLDCOUNT | VAL_TOTAL_AGE | VAL_MAX_AGE | VAL_MIN_AGE | VAL_MEMSIZE;
|
80
|
+
tmp_trace_arg->aggregate_table = st_init_table(&memcmp_hash_type);
|
81
|
+
tmp_trace_arg->object_table = st_init_numtable();
|
82
|
+
tmp_trace_arg->str_table = st_init_strtable();
|
83
|
+
tmp_trace_arg->freed_allocation_info = NULL;
|
84
|
+
tmp_trace_arg->lifetime_table = NULL;
|
85
|
+
}
|
86
|
+
return tmp_trace_arg;
|
87
|
+
}
|
88
|
+
|
89
|
+
/*
|
90
|
+
*
|
91
|
+
* [Internal] Callback for the RUBY_INTERNAL_EVENT_FREEOBJ event
|
92
|
+
*
|
93
|
+
* When an object is freed we check if we are tracing it.
|
94
|
+
* If we are, then we register in the `freed_hash` that it has
|
95
|
+
* been freed by setting the `object_id` key to a value of `true`
|
96
|
+
*
|
97
|
+
*/
|
98
|
+
static void
|
99
|
+
freeobj_i(VALUE tpval, void *data)
|
100
|
+
{
|
101
|
+
// struct traceobj_arg *arg = (struct traceobj_arg *)data;
|
102
|
+
VALUE freed_object_id_hash = rb_ivar_get(rb_mLivingDead, rb_intern("freed_object_id_hash"));
|
103
|
+
VALUE object_id_tracing_hash = rb_ivar_get(rb_mLivingDead, rb_intern("object_id_tracing_hash"));
|
104
|
+
|
105
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
106
|
+
VALUE obj = rb_tracearg_object(tparg);
|
107
|
+
VALUE object_id = rb_obj_id(obj);
|
108
|
+
|
109
|
+
|
110
|
+
// Useful for debugging, we cannot use `rb_p` here since it allocates new memory
|
111
|
+
// Using `rb_p` here will cause a SEGV.
|
112
|
+
//
|
113
|
+
// long oid = NUM2LONG(rb_obj_id(obj));
|
114
|
+
// printf("Freed: %lu\n", oid);
|
115
|
+
|
116
|
+
if (rb_hash_aref(object_id_tracing_hash, object_id) != Qnil ) {
|
117
|
+
rb_hash_aset(freed_object_id_hash, rb_obj_id(obj), Qtrue);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
// Used for debugging only
|
122
|
+
//
|
123
|
+
// static void
|
124
|
+
// newobj_i(VALUE tpval, void *data)
|
125
|
+
// {
|
126
|
+
// // struct traceobj_arg *arg = (struct traceobj_arg *)data;
|
127
|
+
|
128
|
+
// rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
129
|
+
// VALUE obj = rb_tracearg_object(tparg);
|
130
|
+
// long oid = NUM2LONG(rb_obj_id(obj));
|
131
|
+
// printf("NEW: %lu\n", oid);
|
132
|
+
// }
|
133
|
+
|
134
|
+
|
135
|
+
/*
|
136
|
+
*
|
137
|
+
* call-seq:
|
138
|
+
* LivingDead.start -> nil
|
139
|
+
*
|
140
|
+
* Begins tracing object free events
|
141
|
+
*
|
142
|
+
*/
|
143
|
+
static VALUE
|
144
|
+
living_dead_start(VALUE self)
|
145
|
+
{
|
146
|
+
VALUE freeobj_hook;
|
147
|
+
// VALUE newobj_hook;
|
148
|
+
struct traceobj_arg *arg = get_traceobj_arg();
|
149
|
+
|
150
|
+
if ((freeobj_hook = rb_ivar_get(rb_mLivingDead, rb_intern("freeobj_hook"))) == Qnil) {
|
151
|
+
rb_ivar_set(rb_mLivingDead, rb_intern("freeobj_hook"), freeobj_hook = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg));
|
152
|
+
rb_ivar_set(rb_mLivingDead, rb_intern("object_id_tracing_hash"), rb_hash_new());
|
153
|
+
rb_ivar_set(rb_mLivingDead, rb_intern("freed_object_id_hash"), rb_hash_new());
|
154
|
+
|
155
|
+
// new hook for debugging only
|
156
|
+
// rb_ivar_set(rb_mLivingDead, rb_intern("newobj_hook"), newobj_hook = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg));
|
157
|
+
|
158
|
+
// rb_tracepoint_enable(newobj_hook);
|
159
|
+
rb_tracepoint_enable(freeobj_hook);
|
160
|
+
}
|
161
|
+
|
162
|
+
return Qnil;
|
163
|
+
}
|
164
|
+
|
165
|
+
|
166
|
+
/*
|
167
|
+
*
|
168
|
+
* call-seq:
|
169
|
+
* LivingDead.freed_hash -> {}
|
170
|
+
*
|
171
|
+
* Returns a hash of freed object ids
|
172
|
+
*
|
173
|
+
* The keys are the object ID, values are `true` if they have been freed
|
174
|
+
* otherwise `false`. Note you must invoke GC before calling this method
|
175
|
+
* see LivingDead.gc_start
|
176
|
+
*
|
177
|
+
*/
|
178
|
+
static VALUE
|
179
|
+
living_dead_freed_hash(VALUE self)
|
180
|
+
{
|
181
|
+
VALUE freed_object_id_hash = rb_ivar_get(rb_mLivingDead, rb_intern("freed_object_id_hash"));
|
182
|
+
|
183
|
+
return freed_object_id_hash;
|
184
|
+
}
|
185
|
+
|
186
|
+
/*
|
187
|
+
*
|
188
|
+
* call-seq:
|
189
|
+
* LivingDead.tracing_hash -> {}
|
190
|
+
*
|
191
|
+
* Returns a hash of traced object ids
|
192
|
+
*
|
193
|
+
* The keys are the object ID of the object being traced, values are all truthy.
|
194
|
+
* In `LivingDead.trace` we set the value to be an instance of `LivingDead::ObjectTrace`.
|
195
|
+
*
|
196
|
+
*/
|
197
|
+
static VALUE
|
198
|
+
living_dead_tracing_hash(VALUE self)
|
199
|
+
{
|
200
|
+
VALUE freed_object_id_hash = rb_ivar_get(rb_mLivingDead, rb_intern("object_id_tracing_hash"));
|
201
|
+
|
202
|
+
return freed_object_id_hash;
|
203
|
+
}
|
204
|
+
|
205
|
+
|
206
|
+
void
|
207
|
+
Init_living_dead(void)
|
208
|
+
{
|
209
|
+
VALUE mod = rb_mLivingDead = rb_const_get(rb_cObject, rb_intern("LivingDead"));
|
210
|
+
|
211
|
+
// LivingDead.trace is a ruby level method
|
212
|
+
// rb_define_module_function(mod, "trace", living_dead_trace, 1);
|
213
|
+
rb_define_module_function(mod, "start", living_dead_start, 0);
|
214
|
+
rb_define_module_function(mod, "freed_hash", living_dead_freed_hash, 0);
|
215
|
+
rb_define_module_function(mod, "tracing_hash", living_dead_tracing_hash, 0);
|
216
|
+
|
217
|
+
}
|
data/lib/living_dead.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "living_dead/version"
|
2
|
+
require "living_dead/living_dead"
|
3
|
+
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
module LivingDead
|
7
|
+
|
8
|
+
@string_io = StringIO.new
|
9
|
+
|
10
|
+
def self.trace(*args)
|
11
|
+
self.start
|
12
|
+
trace = ObjectTrace.new(*args)
|
13
|
+
|
14
|
+
self.tracing_hash[trace.key] = trace
|
15
|
+
self.freed_hash[trace.key] = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.traced_objects
|
19
|
+
gc_start
|
20
|
+
tracing_hash.map do |_, trace|
|
21
|
+
trace
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
# GIANT BALL OF HACKS || THERE BE DRAGONS
|
27
|
+
#
|
28
|
+
# There is so much I don't understand on why I need to do the things
|
29
|
+
# I'm doing in this method.
|
30
|
+
def self.gc_start
|
31
|
+
# During debugging I found calling "puts" made some things
|
32
|
+
# mysteriously work, I have no idea why. If you remove this line
|
33
|
+
# then (more) tests fail. Maybe it has something to do with the way
|
34
|
+
# GC interacts with IO? I seriously have no idea.
|
35
|
+
#
|
36
|
+
@string_io.puts "=="
|
37
|
+
|
38
|
+
# Calling flush so we don't create a memory leak.
|
39
|
+
# Funny enough maybe calling flush without `puts` also works?
|
40
|
+
# IDK
|
41
|
+
#
|
42
|
+
@string_io.flush
|
43
|
+
|
44
|
+
# Why do we need this? Well I'll tell you...
|
45
|
+
# LivingDead calling `singleton_class.instance_eval` does not retain in the simple case
|
46
|
+
# fails without this.
|
47
|
+
#
|
48
|
+
LivingDead.freed_hash
|
49
|
+
|
50
|
+
# Calling GC multiple times fixes a different class of things
|
51
|
+
# Specifically the singleton_class.instance_eval tests.
|
52
|
+
# It might also be related to calling GC in a block, but changing
|
53
|
+
# to 1.times brings back failures.
|
54
|
+
#
|
55
|
+
# Calling 2 times results in eventual failure https://twitter.com/schneems/status/804369346910896128
|
56
|
+
# Calling 5 times results in eventual failure https://twitter.com/schneems/status/804382968307445760
|
57
|
+
# Trying 10 times
|
58
|
+
#
|
59
|
+
10.times { GC.start }
|
60
|
+
end
|
61
|
+
public
|
62
|
+
|
63
|
+
def self.freed_objects
|
64
|
+
gc_start
|
65
|
+
freed_hash.map do |key, _|
|
66
|
+
tracing_hash[key]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class ObjectTrace
|
71
|
+
def initialize(obj = nil, object_id: nil, to_s: nil)
|
72
|
+
@object_id = object_id || obj.object_id
|
73
|
+
@to_s = to_s&.dup || obj.to_s.dup
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
"#<LivingDead::ObjectTrace:#{ "0x#{ (object_id << 1).to_s(16) }" } @object_id=#{@object_id} @to_s=#{ @to_s.inspect }, @freed=#{ freed? }>"
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect
|
81
|
+
to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
def retained?
|
85
|
+
!freed?
|
86
|
+
end
|
87
|
+
|
88
|
+
def freed?
|
89
|
+
!!LivingDead.freed_hash[@object_id]
|
90
|
+
end
|
91
|
+
|
92
|
+
def key
|
93
|
+
@object_id
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|