thread_safety 0.1.0
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/.rubocop.yml +8 -0
- data/README.md +18 -0
- data/Rakefile +36 -0
- data/exe/thread_safety +17 -0
- data/ext/ccan/build_assert/build_assert.h +40 -0
- data/ext/ccan/check_type/check_type.h +63 -0
- data/ext/ccan/container_of/container_of.h +142 -0
- data/ext/ccan/licenses/BSD-MIT +17 -0
- data/ext/ccan/licenses/CC0 +28 -0
- data/ext/ccan/list/list.h +791 -0
- data/ext/ccan/str/str.h +17 -0
- data/ext/darray.h +278 -0
- data/ext/extconf.rb +5 -0
- data/ext/extconf_base.rb +14 -0
- data/ext/gc/default/default.c +9442 -0
- data/ext/gc/gc.h +232 -0
- data/ext/gc/gc_impl.h +127 -0
- data/ext/thread_safety.c +90 -0
- data/lib/thread_safety/offense.rb +31 -0
- data/lib/thread_safety/version.rb +5 -0
- data/lib/thread_safety.rb +32 -0
- metadata +77 -0
data/ext/gc/gc.h
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
#ifndef GC_GC_H
|
2
|
+
#define GC_GC_H
|
3
|
+
/**
|
4
|
+
* @author Ruby developers <ruby-core@ruby-lang.org>
|
5
|
+
* @copyright This file is a part of the programming language Ruby.
|
6
|
+
* Permission is hereby granted, to either redistribute and/or
|
7
|
+
* modify this file, provided that the conditions mentioned in the
|
8
|
+
* file COPYING are met. Consult the file for details.
|
9
|
+
* @brief Private header for the default GC and other GC implementations
|
10
|
+
* first introduced for [Feature #20470].
|
11
|
+
*/
|
12
|
+
#include "ruby/ruby.h"
|
13
|
+
|
14
|
+
#if USE_MODULAR_GC
|
15
|
+
#include "ruby/thread_native.h"
|
16
|
+
|
17
|
+
struct rb_gc_vm_context {
|
18
|
+
rb_nativethread_lock_t lock;
|
19
|
+
|
20
|
+
struct rb_execution_context_struct *ec;
|
21
|
+
};
|
22
|
+
#endif
|
23
|
+
|
24
|
+
typedef int (*vm_table_foreach_callback_func)(VALUE value, void *data);
|
25
|
+
typedef int (*vm_table_update_callback_func)(VALUE *value, void *data);
|
26
|
+
|
27
|
+
enum rb_gc_vm_weak_tables {
|
28
|
+
RB_GC_VM_CI_TABLE,
|
29
|
+
RB_GC_VM_OVERLOADED_CME_TABLE,
|
30
|
+
RB_GC_VM_GLOBAL_SYMBOLS_TABLE,
|
31
|
+
RB_GC_VM_ID2REF_TABLE,
|
32
|
+
RB_GC_VM_GENERIC_FIELDS_TABLE,
|
33
|
+
RB_GC_VM_FROZEN_STRINGS_TABLE,
|
34
|
+
RB_GC_VM_WEAK_TABLE_COUNT
|
35
|
+
};
|
36
|
+
|
37
|
+
RUBY_SYMBOL_EXPORT_BEGIN
|
38
|
+
unsigned int rb_gc_vm_lock(void);
|
39
|
+
void rb_gc_vm_unlock(unsigned int lev);
|
40
|
+
unsigned int rb_gc_cr_lock(void);
|
41
|
+
void rb_gc_cr_unlock(unsigned int lev);
|
42
|
+
unsigned int rb_gc_vm_lock_no_barrier(void);
|
43
|
+
void rb_gc_vm_unlock_no_barrier(unsigned int lev);
|
44
|
+
void rb_gc_vm_barrier(void);
|
45
|
+
size_t rb_gc_obj_optimal_size(VALUE obj);
|
46
|
+
void rb_gc_mark_children(void *objspace, VALUE obj);
|
47
|
+
void rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, vm_table_update_callback_func update_callback, void *data, bool weak_only, enum rb_gc_vm_weak_tables table);
|
48
|
+
void rb_gc_update_object_references(void *objspace, VALUE obj);
|
49
|
+
void rb_gc_update_vm_references(void *objspace);
|
50
|
+
void rb_gc_event_hook(VALUE obj, rb_event_flag_t event);
|
51
|
+
void *rb_gc_get_objspace(void);
|
52
|
+
size_t rb_size_mul_or_raise(size_t x, size_t y, VALUE exc);
|
53
|
+
void rb_gc_run_obj_finalizer(VALUE objid, long count, VALUE (*callback)(long i, void *data), void *data);
|
54
|
+
void rb_gc_set_pending_interrupt(void);
|
55
|
+
void rb_gc_unset_pending_interrupt(void);
|
56
|
+
void rb_gc_obj_free_vm_weak_references(VALUE obj);
|
57
|
+
bool rb_gc_obj_free(void *objspace, VALUE obj);
|
58
|
+
void rb_gc_save_machine_context(void);
|
59
|
+
void rb_gc_mark_roots(void *objspace, const char **categoryp);
|
60
|
+
void rb_gc_ractor_newobj_cache_foreach(void (*func)(void *cache, void *data), void *data);
|
61
|
+
bool rb_gc_multi_ractor_p(void);
|
62
|
+
void rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, void *), void *passing_data);
|
63
|
+
void rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), void *data);
|
64
|
+
void rb_obj_info_dump(VALUE obj);
|
65
|
+
const char *rb_obj_info(VALUE obj);
|
66
|
+
bool rb_gc_shutdown_call_finalizer_p(VALUE obj);
|
67
|
+
uint32_t rb_gc_get_shape(VALUE obj);
|
68
|
+
void rb_gc_set_shape(VALUE obj, uint32_t shape_id);
|
69
|
+
uint32_t rb_gc_rebuild_shape(VALUE obj, size_t heap_id);
|
70
|
+
size_t rb_obj_memsize_of(VALUE obj);
|
71
|
+
void rb_gc_prepare_heap_process_object(VALUE obj);
|
72
|
+
bool ruby_free_at_exit_p(void);
|
73
|
+
bool rb_memerror_reentered(void);
|
74
|
+
bool rb_obj_id_p(VALUE);
|
75
|
+
|
76
|
+
#if USE_MODULAR_GC
|
77
|
+
bool rb_gc_event_hook_required_p(rb_event_flag_t event);
|
78
|
+
void *rb_gc_get_ractor_newobj_cache(void);
|
79
|
+
void rb_gc_initialize_vm_context(struct rb_gc_vm_context *context);
|
80
|
+
void rb_gc_worker_thread_set_vm_context(struct rb_gc_vm_context *context);
|
81
|
+
void rb_gc_worker_thread_unset_vm_context(struct rb_gc_vm_context *context);
|
82
|
+
#endif
|
83
|
+
RUBY_SYMBOL_EXPORT_END
|
84
|
+
|
85
|
+
void rb_ractor_finish_marking(void);
|
86
|
+
|
87
|
+
// -------------------Private section begin------------------------
|
88
|
+
// Functions in this section are private to the default GC and gc.c
|
89
|
+
|
90
|
+
#ifdef BUILDING_MODULAR_GC
|
91
|
+
RBIMPL_WARNING_PUSH()
|
92
|
+
RBIMPL_WARNING_IGNORED(-Wunused-function)
|
93
|
+
#endif
|
94
|
+
|
95
|
+
/* RGENGC_CHECK_MODE
|
96
|
+
* 0: disable all assertions
|
97
|
+
* 1: enable assertions (to debug RGenGC)
|
98
|
+
* 2: enable internal consistency check at each GC (for debugging)
|
99
|
+
* 3: enable internal consistency check at each GC steps (for debugging)
|
100
|
+
* 4: enable liveness check
|
101
|
+
* 5: show all references
|
102
|
+
*/
|
103
|
+
#ifndef RGENGC_CHECK_MODE
|
104
|
+
# define RGENGC_CHECK_MODE 0
|
105
|
+
#endif
|
106
|
+
|
107
|
+
#ifndef GC_ASSERT
|
108
|
+
# define GC_ASSERT(expr) RUBY_ASSERT_MESG_WHEN(RGENGC_CHECK_MODE > 0, expr, #expr)
|
109
|
+
#endif
|
110
|
+
|
111
|
+
static int
|
112
|
+
hash_foreach_replace_value(st_data_t key, st_data_t value, st_data_t argp, int error)
|
113
|
+
{
|
114
|
+
if (rb_gc_location((VALUE)value) != (VALUE)value) {
|
115
|
+
return ST_REPLACE;
|
116
|
+
}
|
117
|
+
return ST_CONTINUE;
|
118
|
+
}
|
119
|
+
|
120
|
+
static int
|
121
|
+
hash_replace_ref_value(st_data_t *key, st_data_t *value, st_data_t argp, int existing)
|
122
|
+
{
|
123
|
+
*value = rb_gc_location((VALUE)*value);
|
124
|
+
|
125
|
+
return ST_CONTINUE;
|
126
|
+
}
|
127
|
+
|
128
|
+
static void
|
129
|
+
gc_ref_update_table_values_only(st_table *tbl)
|
130
|
+
{
|
131
|
+
if (!tbl || tbl->num_entries == 0) return;
|
132
|
+
|
133
|
+
if (st_foreach_with_replace(tbl, hash_foreach_replace_value, hash_replace_ref_value, 0)) {
|
134
|
+
rb_raise(rb_eRuntimeError, "hash modified during iteration");
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
static int
|
139
|
+
gc_mark_tbl_no_pin_i(st_data_t key, st_data_t value, st_data_t data)
|
140
|
+
{
|
141
|
+
rb_gc_mark_movable((VALUE)value);
|
142
|
+
|
143
|
+
return ST_CONTINUE;
|
144
|
+
}
|
145
|
+
|
146
|
+
static int
|
147
|
+
hash_foreach_replace(st_data_t key, st_data_t value, st_data_t argp, int error)
|
148
|
+
{
|
149
|
+
if (rb_gc_location((VALUE)key) != (VALUE)key) {
|
150
|
+
return ST_REPLACE;
|
151
|
+
}
|
152
|
+
|
153
|
+
if (rb_gc_location((VALUE)value) != (VALUE)value) {
|
154
|
+
return ST_REPLACE;
|
155
|
+
}
|
156
|
+
|
157
|
+
return ST_CONTINUE;
|
158
|
+
}
|
159
|
+
|
160
|
+
static int
|
161
|
+
hash_replace_ref(st_data_t *key, st_data_t *value, st_data_t argp, int existing)
|
162
|
+
{
|
163
|
+
if (rb_gc_location((VALUE)*key) != (VALUE)*key) {
|
164
|
+
*key = rb_gc_location((VALUE)*key);
|
165
|
+
}
|
166
|
+
|
167
|
+
if (rb_gc_location((VALUE)*value) != (VALUE)*value) {
|
168
|
+
*value = rb_gc_location((VALUE)*value);
|
169
|
+
}
|
170
|
+
|
171
|
+
return ST_CONTINUE;
|
172
|
+
}
|
173
|
+
|
174
|
+
static void
|
175
|
+
gc_update_table_refs(st_table *tbl)
|
176
|
+
{
|
177
|
+
if (!tbl || tbl->num_entries == 0) return;
|
178
|
+
|
179
|
+
if (st_foreach_with_replace(tbl, hash_foreach_replace, hash_replace_ref, 0)) {
|
180
|
+
rb_raise(rb_eRuntimeError, "hash modified during iteration");
|
181
|
+
}
|
182
|
+
}
|
183
|
+
|
184
|
+
static inline size_t
|
185
|
+
xmalloc2_size(const size_t count, const size_t elsize)
|
186
|
+
{
|
187
|
+
return rb_size_mul_or_raise(count, elsize, rb_eArgError);
|
188
|
+
}
|
189
|
+
|
190
|
+
static VALUE
|
191
|
+
type_sym(size_t type)
|
192
|
+
{
|
193
|
+
switch (type) {
|
194
|
+
#define COUNT_TYPE(t) case (t): return ID2SYM(rb_intern(#t)); break;
|
195
|
+
COUNT_TYPE(T_NONE);
|
196
|
+
COUNT_TYPE(T_OBJECT);
|
197
|
+
COUNT_TYPE(T_CLASS);
|
198
|
+
COUNT_TYPE(T_MODULE);
|
199
|
+
COUNT_TYPE(T_FLOAT);
|
200
|
+
COUNT_TYPE(T_STRING);
|
201
|
+
COUNT_TYPE(T_REGEXP);
|
202
|
+
COUNT_TYPE(T_ARRAY);
|
203
|
+
COUNT_TYPE(T_HASH);
|
204
|
+
COUNT_TYPE(T_STRUCT);
|
205
|
+
COUNT_TYPE(T_BIGNUM);
|
206
|
+
COUNT_TYPE(T_FILE);
|
207
|
+
COUNT_TYPE(T_DATA);
|
208
|
+
COUNT_TYPE(T_MATCH);
|
209
|
+
COUNT_TYPE(T_COMPLEX);
|
210
|
+
COUNT_TYPE(T_RATIONAL);
|
211
|
+
COUNT_TYPE(T_NIL);
|
212
|
+
COUNT_TYPE(T_TRUE);
|
213
|
+
COUNT_TYPE(T_FALSE);
|
214
|
+
COUNT_TYPE(T_SYMBOL);
|
215
|
+
COUNT_TYPE(T_FIXNUM);
|
216
|
+
COUNT_TYPE(T_IMEMO);
|
217
|
+
COUNT_TYPE(T_UNDEF);
|
218
|
+
COUNT_TYPE(T_NODE);
|
219
|
+
COUNT_TYPE(T_ICLASS);
|
220
|
+
COUNT_TYPE(T_ZOMBIE);
|
221
|
+
COUNT_TYPE(T_MOVED);
|
222
|
+
#undef COUNT_TYPE
|
223
|
+
default: return SIZET2NUM(type); break;
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
#ifdef BUILDING_MODULAR_GC
|
228
|
+
RBIMPL_WARNING_POP()
|
229
|
+
#endif
|
230
|
+
// -------------------Private section end------------------------
|
231
|
+
|
232
|
+
#endif
|
data/ext/gc/gc_impl.h
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
#ifndef GC_GC_IMPL_H
|
2
|
+
#define GC_GC_IMPL_H
|
3
|
+
/**
|
4
|
+
* @author Ruby developers <ruby-core@ruby-lang.org>
|
5
|
+
* @copyright This file is a part of the programming language Ruby.
|
6
|
+
* Permission is hereby granted, to either redistribute and/or
|
7
|
+
* modify this file, provided that the conditions mentioned in the
|
8
|
+
* file COPYING are met. Consult the file for details.
|
9
|
+
* @brief Header for GC implementations introduced in [Feature #20470].
|
10
|
+
*/
|
11
|
+
#include "ruby/ruby.h"
|
12
|
+
|
13
|
+
#ifndef RB_GC_OBJECT_METADATA_ENTRY_DEFINED
|
14
|
+
# define RB_GC_OBJECT_METADATA_ENTRY_DEFINED
|
15
|
+
struct rb_gc_object_metadata_entry {
|
16
|
+
ID name;
|
17
|
+
VALUE val;
|
18
|
+
};
|
19
|
+
#endif
|
20
|
+
|
21
|
+
#ifdef BUILDING_MODULAR_GC
|
22
|
+
# define GC_IMPL_FN
|
23
|
+
#else
|
24
|
+
// `GC_IMPL_FN` is an implementation detail of `!USE_MODULAR_GC` builds
|
25
|
+
// to have the default GC in the same translation unit as gc.c for
|
26
|
+
// the sake of optimizer visibility. It expands to nothing unless
|
27
|
+
// you're the default GC.
|
28
|
+
//
|
29
|
+
// For the default GC, do not copy-paste this when implementing
|
30
|
+
// these functions. This takes advantage of internal linkage winning
|
31
|
+
// when appearing first. See C99 6.2.2p4.
|
32
|
+
# define GC_IMPL_FN static
|
33
|
+
#endif
|
34
|
+
|
35
|
+
// Bootup
|
36
|
+
GC_IMPL_FN void *rb_gc_impl_objspace_alloc(void);
|
37
|
+
GC_IMPL_FN void rb_gc_impl_objspace_init(void *objspace_ptr);
|
38
|
+
GC_IMPL_FN void *rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor);
|
39
|
+
GC_IMPL_FN void rb_gc_impl_set_params(void *objspace_ptr);
|
40
|
+
GC_IMPL_FN void rb_gc_impl_init(void);
|
41
|
+
GC_IMPL_FN size_t *rb_gc_impl_heap_sizes(void *objspace_ptr);
|
42
|
+
// Shutdown
|
43
|
+
GC_IMPL_FN void rb_gc_impl_shutdown_free_objects(void *objspace_ptr);
|
44
|
+
GC_IMPL_FN void rb_gc_impl_objspace_free(void *objspace_ptr);
|
45
|
+
GC_IMPL_FN void rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache);
|
46
|
+
// GC
|
47
|
+
GC_IMPL_FN void rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact);
|
48
|
+
GC_IMPL_FN bool rb_gc_impl_during_gc_p(void *objspace_ptr);
|
49
|
+
GC_IMPL_FN void rb_gc_impl_prepare_heap(void *objspace_ptr);
|
50
|
+
GC_IMPL_FN void rb_gc_impl_gc_enable(void *objspace_ptr);
|
51
|
+
GC_IMPL_FN void rb_gc_impl_gc_disable(void *objspace_ptr, bool finish_current_gc);
|
52
|
+
GC_IMPL_FN bool rb_gc_impl_gc_enabled_p(void *objspace_ptr);
|
53
|
+
GC_IMPL_FN void rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag);
|
54
|
+
GC_IMPL_FN VALUE rb_gc_impl_stress_get(void *objspace_ptr);
|
55
|
+
GC_IMPL_FN VALUE rb_gc_impl_config_get(void *objspace_ptr);
|
56
|
+
GC_IMPL_FN void rb_gc_impl_config_set(void *objspace_ptr, VALUE hash);
|
57
|
+
// Object allocation
|
58
|
+
GC_IMPL_FN VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, bool wb_protected, size_t alloc_size);
|
59
|
+
GC_IMPL_FN size_t rb_gc_impl_obj_slot_size(VALUE obj);
|
60
|
+
GC_IMPL_FN size_t rb_gc_impl_heap_id_for_size(void *objspace_ptr, size_t size);
|
61
|
+
GC_IMPL_FN bool rb_gc_impl_size_allocatable_p(size_t size);
|
62
|
+
// Malloc
|
63
|
+
/*
|
64
|
+
* BEWARE: These functions may or may not run under GVL.
|
65
|
+
*
|
66
|
+
* You might want to make them thread-safe.
|
67
|
+
* Garbage collecting inside is possible if and only if you
|
68
|
+
* already have GVL. Also raising exceptions without one is a
|
69
|
+
* total disaster.
|
70
|
+
*
|
71
|
+
* When you absolutely cannot allocate the requested amount of
|
72
|
+
* memory just return NULL (with appropriate errno set).
|
73
|
+
* The caller side takes care of that situation.
|
74
|
+
*/
|
75
|
+
GC_IMPL_FN void *rb_gc_impl_malloc(void *objspace_ptr, size_t size);
|
76
|
+
GC_IMPL_FN void *rb_gc_impl_calloc(void *objspace_ptr, size_t size);
|
77
|
+
GC_IMPL_FN void *rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size);
|
78
|
+
GC_IMPL_FN void rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size);
|
79
|
+
GC_IMPL_FN void rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff);
|
80
|
+
// Marking
|
81
|
+
GC_IMPL_FN void rb_gc_impl_mark(void *objspace_ptr, VALUE obj);
|
82
|
+
GC_IMPL_FN void rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr);
|
83
|
+
GC_IMPL_FN void rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj);
|
84
|
+
GC_IMPL_FN void rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj);
|
85
|
+
GC_IMPL_FN void rb_gc_impl_mark_weak(void *objspace_ptr, VALUE *ptr);
|
86
|
+
GC_IMPL_FN void rb_gc_impl_remove_weak(void *objspace_ptr, VALUE parent_obj, VALUE *ptr);
|
87
|
+
// Compaction
|
88
|
+
GC_IMPL_FN bool rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj);
|
89
|
+
GC_IMPL_FN VALUE rb_gc_impl_location(void *objspace_ptr, VALUE value);
|
90
|
+
// Write barriers
|
91
|
+
GC_IMPL_FN void rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b);
|
92
|
+
GC_IMPL_FN void rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj);
|
93
|
+
GC_IMPL_FN void rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj);
|
94
|
+
// Heap walking
|
95
|
+
GC_IMPL_FN void rb_gc_impl_each_objects(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data);
|
96
|
+
GC_IMPL_FN void rb_gc_impl_each_object(void *objspace_ptr, void (*func)(VALUE obj, void *data), void *data);
|
97
|
+
// Finalizers
|
98
|
+
GC_IMPL_FN void rb_gc_impl_make_zombie(void *objspace_ptr, VALUE obj, void (*dfree)(void *), void *data);
|
99
|
+
GC_IMPL_FN VALUE rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block);
|
100
|
+
GC_IMPL_FN void rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj);
|
101
|
+
GC_IMPL_FN void rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj);
|
102
|
+
GC_IMPL_FN void rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr);
|
103
|
+
// Object ID
|
104
|
+
GC_IMPL_FN VALUE rb_gc_impl_object_id(void *objspace_ptr, VALUE obj);
|
105
|
+
GC_IMPL_FN VALUE rb_gc_impl_object_id_to_ref(void *objspace_ptr, VALUE object_id);
|
106
|
+
// Forking
|
107
|
+
GC_IMPL_FN void rb_gc_impl_before_fork(void *objspace_ptr);
|
108
|
+
GC_IMPL_FN void rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid);
|
109
|
+
// Statistics
|
110
|
+
GC_IMPL_FN void rb_gc_impl_set_measure_total_time(void *objspace_ptr, VALUE flag);
|
111
|
+
GC_IMPL_FN bool rb_gc_impl_get_measure_total_time(void *objspace_ptr);
|
112
|
+
GC_IMPL_FN unsigned long long rb_gc_impl_get_total_time(void *objspace_ptr);
|
113
|
+
GC_IMPL_FN size_t rb_gc_impl_gc_count(void *objspace_ptr);
|
114
|
+
GC_IMPL_FN VALUE rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE key);
|
115
|
+
GC_IMPL_FN VALUE rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym);
|
116
|
+
GC_IMPL_FN VALUE rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym);
|
117
|
+
GC_IMPL_FN const char *rb_gc_impl_active_gc_name(void);
|
118
|
+
// Miscellaneous
|
119
|
+
GC_IMPL_FN struct rb_gc_object_metadata_entry *rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj);
|
120
|
+
GC_IMPL_FN bool rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr);
|
121
|
+
GC_IMPL_FN bool rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE obj);
|
122
|
+
GC_IMPL_FN void rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event);
|
123
|
+
GC_IMPL_FN void rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj);
|
124
|
+
|
125
|
+
#undef GC_IMPL_FN
|
126
|
+
|
127
|
+
#endif
|
data/ext/thread_safety.c
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#include "ruby/ruby.h"
|
2
|
+
#include "ruby/debug.h"
|
3
|
+
#include "gc/gc.h"
|
4
|
+
#include "gc/gc_impl.h"
|
5
|
+
|
6
|
+
#define RVALUE_OVERHEAD (sizeof(VALUE))
|
7
|
+
|
8
|
+
static VALUE main_thread_id = INT2FIX(-1);
|
9
|
+
static VALUE mThreadSafety;
|
10
|
+
static ID id_report_offense, id_thread_safety_disabled;
|
11
|
+
|
12
|
+
size_t rb_gc_impl_obj_slot_size(VALUE obj);
|
13
|
+
|
14
|
+
static VALUE
|
15
|
+
get_owner_thread_id(VALUE obj)
|
16
|
+
{
|
17
|
+
return *(VALUE *)(obj + rb_gc_impl_obj_slot_size(obj));
|
18
|
+
}
|
19
|
+
|
20
|
+
static VALUE
|
21
|
+
get_current_thread_id(void)
|
22
|
+
{
|
23
|
+
VALUE thread = rb_thread_current();
|
24
|
+
|
25
|
+
if (thread == 0 || thread == rb_thread_main()) {
|
26
|
+
return main_thread_id;
|
27
|
+
}
|
28
|
+
|
29
|
+
return rb_obj_id(thread);
|
30
|
+
}
|
31
|
+
|
32
|
+
#define PATCH_GC_FUNC(ret_type, name, ...) \
|
33
|
+
ret_type rb_gc_impl_##name##_original(__VA_ARGS__); \
|
34
|
+
ret_type rb_gc_impl_##name(__VA_ARGS__)
|
35
|
+
|
36
|
+
PATCH_GC_FUNC(VALUE, new_obj, void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, bool wb_protected, size_t alloc_size)
|
37
|
+
{
|
38
|
+
VALUE obj = rb_gc_impl_new_obj_original(objspace_ptr, cache_ptr, klass, flags, v1, v2, v3, wb_protected, alloc_size);
|
39
|
+
|
40
|
+
*((VALUE *)(obj + rb_gc_impl_obj_slot_size(obj))) = get_current_thread_id();
|
41
|
+
|
42
|
+
return obj;
|
43
|
+
}
|
44
|
+
#define rb_gc_impl_new_obj rb_gc_impl_new_obj_original
|
45
|
+
|
46
|
+
PATCH_GC_FUNC(void, writebarrier, void *objspace_ptr, VALUE a, VALUE b)
|
47
|
+
{
|
48
|
+
VALUE owner_thread_id = get_owner_thread_id(a);
|
49
|
+
VALUE current_thread_id = get_current_thread_id();
|
50
|
+
VALUE current_thread = rb_thread_current();
|
51
|
+
if (owner_thread_id != current_thread_id &&
|
52
|
+
// Skip imemo types like call caches.
|
53
|
+
RB_BUILTIN_TYPE(a) != T_IMEMO &&
|
54
|
+
// Skip imemo types like call caches.
|
55
|
+
(RB_IMMEDIATE_P(b) || RB_BUILTIN_TYPE(b) != T_IMEMO) &&
|
56
|
+
// Class of 0 means that it is a hidden object.
|
57
|
+
CLASS_OF(a) != 0 &&
|
58
|
+
// When we create a new thread, the owner is the parent thread but
|
59
|
+
// may be written to during execution.
|
60
|
+
CLASS_OF(a) != rb_cThread &&
|
61
|
+
!RTEST(rb_thread_local_aref(current_thread, id_thread_safety_disabled))) {
|
62
|
+
bool prev_gc_enabled = !rb_gc_impl_gc_enabled_p(objspace_ptr);
|
63
|
+
if (prev_gc_enabled) rb_gc_impl_gc_disable(objspace_ptr, false);
|
64
|
+
|
65
|
+
rb_thread_local_aset(current_thread, id_thread_safety_disabled, Qtrue);
|
66
|
+
|
67
|
+
rb_funcall(mThreadSafety, id_report_offense, 3, a, owner_thread_id, rb_thread_current());
|
68
|
+
|
69
|
+
rb_thread_local_aset(current_thread, id_thread_safety_disabled, Qfalse);
|
70
|
+
|
71
|
+
if (prev_gc_enabled) rb_gc_impl_gc_enable(objspace_ptr);
|
72
|
+
}
|
73
|
+
|
74
|
+
rb_gc_impl_writebarrier_original(objspace_ptr, a, b);
|
75
|
+
}
|
76
|
+
#define rb_gc_impl_writebarrier rb_gc_impl_writebarrier_original
|
77
|
+
|
78
|
+
PATCH_GC_FUNC(void, init, void)
|
79
|
+
{
|
80
|
+
rb_gc_register_address(&mThreadSafety);
|
81
|
+
mThreadSafety = rb_define_module("ThreadSafety");
|
82
|
+
|
83
|
+
id_report_offense = rb_intern("report_offense");
|
84
|
+
id_thread_safety_disabled = rb_intern("thread_safety_disabled");
|
85
|
+
|
86
|
+
rb_gc_impl_init_original();
|
87
|
+
}
|
88
|
+
#define rb_gc_impl_init rb_gc_impl_init_original
|
89
|
+
|
90
|
+
#include "gc/default/default.c"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThreadSafety
|
4
|
+
class Offense
|
5
|
+
attr_reader :object, :backtrace, :access_thread
|
6
|
+
|
7
|
+
def initialize(object, created_thread_obj_id, access_thread)
|
8
|
+
@object = object
|
9
|
+
@created_thread_obj_id = created_thread_obj_id
|
10
|
+
@access_thread = access_thread
|
11
|
+
@backtrace = caller_locations(2).freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def created_thread
|
15
|
+
@created_thread = find_thread_from_obj_id(@created_thread_obj_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"#<ThreadSafety::Offense object=#{object} created_thread=#{created_thread} access_thread=#{access_thread} backtrace=#{backtrace[0]}>"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def find_thread_from_obj_id(obj_id)
|
25
|
+
# -1 is the main_thread_id
|
26
|
+
return Thread.main if obj_id == -1
|
27
|
+
|
28
|
+
Thread.list.find { |t| t.object_id == obj_id }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "thread_safety/offense"
|
4
|
+
require_relative "thread_safety/version"
|
5
|
+
|
6
|
+
module ThreadSafety
|
7
|
+
class << self
|
8
|
+
attr_reader :callback
|
9
|
+
|
10
|
+
def callback=(cb)
|
11
|
+
@callback = cb
|
12
|
+
end
|
13
|
+
|
14
|
+
def suppress_warnings(&block)
|
15
|
+
original_thread_safety_disabled = Thread.current[:thread_safety_disabled]
|
16
|
+
Thread.current[:thread_safety_disabled] = true
|
17
|
+
yield
|
18
|
+
ensure
|
19
|
+
Thread.current[:thread_safety_disabled] = original_thread_safety_disabled
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def report_offense(...)
|
25
|
+
return unless callback
|
26
|
+
|
27
|
+
callback.call(Offense.new(...))
|
28
|
+
rescue => e
|
29
|
+
$stderr.puts "[ThreadSafety]: callback raised #{e.inspect}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thread_safety
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Zhu
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: rake-compiler
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
email:
|
27
|
+
- peter@peterzhu.ca
|
28
|
+
executables:
|
29
|
+
- thread_safety
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- ".rubocop.yml"
|
34
|
+
- README.md
|
35
|
+
- Rakefile
|
36
|
+
- exe/thread_safety
|
37
|
+
- ext/ccan/build_assert/build_assert.h
|
38
|
+
- ext/ccan/check_type/check_type.h
|
39
|
+
- ext/ccan/container_of/container_of.h
|
40
|
+
- ext/ccan/licenses/BSD-MIT
|
41
|
+
- ext/ccan/licenses/CC0
|
42
|
+
- ext/ccan/list/list.h
|
43
|
+
- ext/ccan/str/str.h
|
44
|
+
- ext/darray.h
|
45
|
+
- ext/extconf.rb
|
46
|
+
- ext/extconf_base.rb
|
47
|
+
- ext/gc/default/default.c
|
48
|
+
- ext/gc/gc.h
|
49
|
+
- ext/gc/gc_impl.h
|
50
|
+
- ext/thread_safety.c
|
51
|
+
- lib/thread_safety.rb
|
52
|
+
- lib/thread_safety/offense.rb
|
53
|
+
- lib/thread_safety/version.rb
|
54
|
+
homepage: https://github.com/Shopify/thread_safety
|
55
|
+
licenses: []
|
56
|
+
metadata:
|
57
|
+
homepage_uri: https://github.com/Shopify/thread_safety
|
58
|
+
source_code_uri: https://github.com/Shopify/thread_safety
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 3.5.0
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubygems_version: 3.7.0.dev
|
74
|
+
specification_version: 4
|
75
|
+
summary: Thread Safety is a tool that plugs into Ruby's Modular GC feature to detect
|
76
|
+
potential thread-safety issues.
|
77
|
+
test_files: []
|