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.
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
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThreadSafety
4
+ VERSION = "0.1.0"
5
+ 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: []