living_dead 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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
|
+

|
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
|