ractor-sharing 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.
@@ -0,0 +1,407 @@
1
+ #include "lock.h"
2
+ #include "ruby/st.h"
3
+
4
+ /* Ractor::LockHash - a Hash several Ractors can share.
5
+ *
6
+ * The entries live in an st_table, not in a Ruby Hash. A Hash would be an
7
+ * unshareable object belonging to whichever Ractor built it, and every other
8
+ * Ractor writing into it would be touching another Ractor's object -- a
9
+ * containment violation, and an edge from a shareable object to an unshareable
10
+ * one that only the VM can record. Keys and values are shareable, so with a C
11
+ * table there is no such object at all: the container belongs to nobody.
12
+ *
13
+ * Nothing escapes either: #synchronize yields the LockHash, never the entries.
14
+ * Every write goes through it, and it is a critical section over this one hash:
15
+ * whatever it changes, other Ractors see all of it or none of it.
16
+ *
17
+ * That atomicity costs parallelism. One lock covers the whole hash, so writes
18
+ * to unrelated keys wait for each other; a hash that has to be written to
19
+ * constantly wants one Ractor::LockVar per key instead, and atomicity across
20
+ * separate objects wants Ractor::TVar.
21
+ */
22
+
23
+ static VALUE rb_cRactorLockHash;
24
+
25
+ struct lockhash {
26
+ st_table *tbl; /* shareable VALUE => shareable VALUE */
27
+ struct rs_lock lock;
28
+ };
29
+
30
+ /* Keys are shareable, so they are frozen and their hash is stable. */
31
+ static st_index_t
32
+ lockhash_key_hash(st_data_t key)
33
+ {
34
+ return (st_index_t)NUM2LONG(rb_hash((VALUE)key));
35
+ }
36
+
37
+ static int
38
+ lockhash_key_cmp(st_data_t a, st_data_t b)
39
+ {
40
+ return rb_eql((VALUE)a, (VALUE)b) ? 0 : 1;
41
+ }
42
+
43
+ static const struct st_hash_type lockhash_type = {
44
+ lockhash_key_cmp,
45
+ lockhash_key_hash,
46
+ };
47
+
48
+ static int
49
+ lockhash_mark_i(st_data_t key, st_data_t value, st_data_t arg)
50
+ {
51
+ rb_gc_mark((VALUE)key);
52
+ rb_gc_mark((VALUE)value);
53
+ return ST_CONTINUE;
54
+ }
55
+
56
+ /* Everything in the table is shareable already, so Ractor.make_shareable
57
+ * walking in here through the mark function has nothing left to freeze. */
58
+ static void
59
+ lockhash_mark(void *ptr)
60
+ {
61
+ st_foreach(((struct lockhash *)ptr)->tbl, lockhash_mark_i, 0);
62
+ }
63
+
64
+ static void
65
+ lockhash_free(void *ptr)
66
+ {
67
+ struct lockhash *lh = ptr;
68
+ st_free_table(lh->tbl);
69
+ rs_lock_destroy(&lh->lock);
70
+ ruby_xfree(lh);
71
+ }
72
+
73
+ static size_t
74
+ lockhash_memsize(const void *ptr)
75
+ {
76
+ const struct lockhash *lh = ptr;
77
+ return sizeof(struct lockhash) + st_memsize(lh->tbl);
78
+ }
79
+
80
+ static const rb_data_type_t lockhash_data_type = {
81
+ "Ractor::LockHash",
82
+ {lockhash_mark, lockhash_free, lockhash_memsize, NULL},
83
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE
84
+ };
85
+
86
+ static struct lockhash *
87
+ lockhash_ptr(VALUE self)
88
+ {
89
+ struct lockhash *lh;
90
+ TypedData_Get_Struct(self, struct lockhash, &lockhash_data_type, lh);
91
+ return lh;
92
+ }
93
+
94
+ static void
95
+ lockhash_check_shareable(VALUE val)
96
+ {
97
+ if (RB_UNLIKELY(!rb_ractor_shareable_p(val))) {
98
+ rb_raise(rb_eArgError, "only shareable object are allowed");
99
+ }
100
+ }
101
+
102
+ /* Writes are only allowed from inside this hash's own #synchronize. Outside it
103
+ * the method is not callable at all, the way a private method is not. */
104
+ static void
105
+ lockhash_check_writable(VALUE self, const char *mid)
106
+ {
107
+ VALUE held = rs_held(rb_thread_current());
108
+
109
+ if (held == self) return;
110
+
111
+ if (NIL_P(held)) {
112
+ rb_raise(rb_eNoMethodError,
113
+ "'%s' is only allowed inside %"PRIsVALUE"#synchronize",
114
+ mid, rb_obj_class(self));
115
+ }
116
+ rb_raise(rb_eRactorNestedLock,
117
+ "already inside another %"PRIsVALUE"; "
118
+ "use Ractor::TVar to change several of them together",
119
+ rb_obj_class(held));
120
+ }
121
+
122
+ static VALUE
123
+ lockhash_alloc(VALUE klass)
124
+ {
125
+ struct lockhash *lh;
126
+ VALUE obj = TypedData_Make_Struct(klass, struct lockhash, &lockhash_data_type, lh);
127
+ lh->tbl = st_init_table(&lockhash_type);
128
+ rs_lock_init(&lh->lock);
129
+ return obj;
130
+ }
131
+
132
+ static int
133
+ lockhash_copy_pair(VALUE key, VALUE value, VALUE dest)
134
+ {
135
+ lockhash_check_shareable(key);
136
+ lockhash_check_shareable(value);
137
+ st_insert(((struct lockhash *)dest)->tbl, (st_data_t)key, (st_data_t)value);
138
+ return ST_CONTINUE;
139
+ }
140
+
141
+ static VALUE
142
+ lockhash_initialize(int argc, VALUE *argv, VALUE self)
143
+ {
144
+ rb_check_frozen(self); /* send(:initialize) again would write past the lock */
145
+ VALUE init = Qnil;
146
+
147
+ rb_scan_args(argc, argv, "01", &init);
148
+ if (!NIL_P(init)) {
149
+ init = rb_convert_type(init, T_HASH, "Hash", "to_hash");
150
+ rb_hash_foreach(init, lockhash_copy_pair, (VALUE)lockhash_ptr(self));
151
+ }
152
+ /* The LockHash never changes; the hash it guards does. */
153
+ rb_obj_freeze(self);
154
+ rb_ractor_make_shareable(self);
155
+ return self;
156
+ }
157
+
158
+ /* --- guarded bodies ------------------------------------------------------- */
159
+
160
+ struct lockhash_op {
161
+ VALUE key;
162
+ VALUE value;
163
+ int argc;
164
+ };
165
+
166
+ /* Qundef when absent. */
167
+ static VALUE
168
+ lockhash_lookup(VALUE self, VALUE key)
169
+ {
170
+ st_data_t found;
171
+ return st_lookup(lockhash_ptr(self)->tbl, (st_data_t)key, &found) ? (VALUE)found : Qundef;
172
+ }
173
+
174
+ static VALUE
175
+ lockhash_aref_body(VALUE ptr)
176
+ {
177
+ struct rs_guard_arg *arg = (struct rs_guard_arg *)ptr;
178
+ struct lockhash_op *op = arg->data;
179
+ VALUE found = lockhash_lookup(arg->self, op->key);
180
+ return RB_UNDEF_P(found) ? Qnil : found;
181
+ }
182
+
183
+ static VALUE
184
+ lockhash_fetch_body(VALUE ptr)
185
+ {
186
+ struct rs_guard_arg *arg = (struct rs_guard_arg *)ptr;
187
+ struct lockhash_op *op = arg->data;
188
+ return lockhash_lookup(arg->self, op->key);
189
+ }
190
+
191
+ static VALUE
192
+ lockhash_key_p_body(VALUE ptr)
193
+ {
194
+ struct rs_guard_arg *arg = (struct rs_guard_arg *)ptr;
195
+ struct lockhash_op *op = arg->data;
196
+ return RB_UNDEF_P(lockhash_lookup(arg->self, op->key)) ? Qfalse : Qtrue;
197
+ }
198
+
199
+ static int
200
+ lockhash_collect_key(st_data_t key, st_data_t value, st_data_t ary)
201
+ {
202
+ rb_ary_push((VALUE)ary, (VALUE)key);
203
+ return ST_CONTINUE;
204
+ }
205
+
206
+ static int
207
+ lockhash_collect_pair(st_data_t key, st_data_t value, st_data_t hash)
208
+ {
209
+ rb_hash_aset((VALUE)hash, (VALUE)key, (VALUE)value);
210
+ return ST_CONTINUE;
211
+ }
212
+
213
+ /* A Hash of the entries as they are now; the caller freezes it if it escapes. */
214
+ static VALUE
215
+ lockhash_snapshot(VALUE self)
216
+ {
217
+ VALUE hash = rb_hash_new();
218
+ st_foreach(lockhash_ptr(self)->tbl, lockhash_collect_pair, (st_data_t)hash);
219
+ return hash;
220
+ }
221
+
222
+ static VALUE
223
+ lockhash_keys_body(VALUE ptr)
224
+ {
225
+ struct rs_guard_arg *arg = (struct rs_guard_arg *)ptr;
226
+ VALUE keys = rb_ary_new_capa(lockhash_ptr(arg->self)->tbl->num_entries);
227
+ st_foreach(lockhash_ptr(arg->self)->tbl, lockhash_collect_key, (st_data_t)keys);
228
+ return rb_ractor_make_shareable(keys);
229
+ }
230
+
231
+ static VALUE
232
+ lockhash_to_h_body(VALUE ptr)
233
+ {
234
+ struct rs_guard_arg *arg = (struct rs_guard_arg *)ptr;
235
+ return rb_ractor_make_shareable(lockhash_snapshot(arg->self));
236
+ }
237
+
238
+ static VALUE
239
+ lockhash_sync_body(VALUE ptr)
240
+ {
241
+ struct rs_guard_arg *arg = (struct rs_guard_arg *)ptr;
242
+ return rb_yield(arg->self); /* rs_guarded has marked the lock held */
243
+ }
244
+
245
+ /* --- reads ---------------------------------------------------------------- */
246
+
247
+ #define LOCKHASH_READ(self, body, op) \
248
+ rs_guarded((self), &lockhash_ptr(self)->lock, (body), (op), true, "")
249
+
250
+ /*
251
+ * call-seq:
252
+ * lockhash[key] -> value
253
+ *
254
+ * Reads one entry under the lock, so it never sees a #synchronize half done.
255
+ */
256
+ static VALUE
257
+ lockhash_aref(VALUE self, VALUE key)
258
+ {
259
+ struct lockhash_op op = {key, Qnil, 1};
260
+ return LOCKHASH_READ(self, lockhash_aref_body, &op);
261
+ }
262
+
263
+ static VALUE
264
+ lockhash_fetch(int argc, VALUE *argv, VALUE self)
265
+ {
266
+ struct lockhash_op op = {Qnil, Qnil, argc};
267
+ VALUE found;
268
+
269
+ rb_scan_args(argc, argv, "11", &op.key, &op.value);
270
+ found = LOCKHASH_READ(self, lockhash_fetch_body, &op);
271
+ if (!RB_UNDEF_P(found)) return found;
272
+
273
+ /* The default runs with the lock released. Held, a block that touched this
274
+ * hash again would wait for a lock its own frame is holding. */
275
+ if (rb_block_given_p()) return rb_yield(op.key);
276
+ if (argc > 1) return op.value;
277
+ rb_raise(rb_eKeyError, "key not found: %+"PRIsVALUE, op.key);
278
+ }
279
+
280
+ static VALUE
281
+ lockhash_key_p(VALUE self, VALUE key)
282
+ {
283
+ struct lockhash_op op = {key, Qnil, 1};
284
+ return LOCKHASH_READ(self, lockhash_key_p_body, &op);
285
+ }
286
+
287
+ /*
288
+ * call-seq:
289
+ * lockhash.keys -> array
290
+ * lockhash.to_h -> hash
291
+ *
292
+ * A snapshot taken under the lock, frozen and shareable, of the whole hash as
293
+ * it was at one moment.
294
+ */
295
+ static VALUE
296
+ lockhash_keys(VALUE self)
297
+ {
298
+ return LOCKHASH_READ(self, lockhash_keys_body, NULL);
299
+ }
300
+
301
+ static VALUE
302
+ lockhash_to_h(VALUE self)
303
+ {
304
+ return LOCKHASH_READ(self, lockhash_to_h_body, NULL);
305
+ }
306
+
307
+ /* --- writes: only from inside #synchronize -------------------------------- */
308
+
309
+ /*
310
+ * call-seq:
311
+ * lockhash[key] = value -> value
312
+ *
313
+ * Stores one entry. Only allowed inside this hash's #synchronize, so that
314
+ * reading an entry and writing it back is one step and not two.
315
+ */
316
+ static VALUE
317
+ lockhash_aset(VALUE self, VALUE key, VALUE value)
318
+ {
319
+ lockhash_check_writable(self, "[]=");
320
+ lockhash_check_shareable(key);
321
+ lockhash_check_shareable(value);
322
+ st_insert(lockhash_ptr(self)->tbl, (st_data_t)key, (st_data_t)value);
323
+ RB_OBJ_WRITTEN(self, Qundef, key);
324
+ RB_OBJ_WRITTEN(self, Qundef, value);
325
+ return value;
326
+ }
327
+
328
+ static VALUE
329
+ lockhash_delete(VALUE self, VALUE key)
330
+ {
331
+ st_data_t k = (st_data_t)key, v;
332
+
333
+ lockhash_check_writable(self, "delete");
334
+ return st_delete(lockhash_ptr(self)->tbl, &k, &v) ? (VALUE)v : Qnil;
335
+ }
336
+
337
+ static VALUE
338
+ lockhash_clear(VALUE self)
339
+ {
340
+ lockhash_check_writable(self, "clear");
341
+ st_clear(lockhash_ptr(self)->tbl);
342
+ return self;
343
+ }
344
+
345
+ /*
346
+ * call-seq:
347
+ * lockhash.synchronize {|lockhash| ... } -> object
348
+ *
349
+ * Runs the block holding this hash, and returns the block's value. Everything
350
+ * it changes becomes visible together. The block is handed the LockHash
351
+ * itself, never the Hash inside it, so no reference to the state escapes.
352
+ *
353
+ * board.synchronize {|b| b[:me] = score; b.delete(:stale) }
354
+ *
355
+ * Keep it short: every reader and writer of this hash waits for it.
356
+ */
357
+ static VALUE
358
+ lockhash_synchronize(VALUE self)
359
+ {
360
+ rb_need_block();
361
+ return rs_guarded(self, &lockhash_ptr(self)->lock, lockhash_sync_body, NULL, true, "");
362
+ }
363
+
364
+ static VALUE
365
+ lockhash_inspect_body(VALUE ptr)
366
+ {
367
+ struct rs_guard_arg *arg = (struct rs_guard_arg *)ptr;
368
+ return rb_sprintf("#<%"PRIsVALUE" %+"PRIsVALUE">",
369
+ rb_obj_class(arg->self), lockhash_snapshot(arg->self));
370
+ }
371
+
372
+ /* Reading the table needs the lock, so inspect takes it -- unless this thread
373
+ * is inside some other lock, where taking it could deadlock and inspect must
374
+ * not raise either. Then it just says nothing about the contents. */
375
+ static VALUE
376
+ lockhash_inspect(VALUE self)
377
+ {
378
+ VALUE held = rs_held(rb_thread_current());
379
+
380
+ if (!NIL_P(held) && held != self) {
381
+ return rb_sprintf("#<%"PRIsVALUE" ...>", rb_obj_class(self));
382
+ }
383
+ return LOCKHASH_READ(self, lockhash_inspect_body, NULL);
384
+ }
385
+
386
+ void
387
+ Init_lockhash_class(void)
388
+ {
389
+ rb_cRactorLockHash = rb_define_class_under(rb_cRactor, "LockHash", rb_cObject);
390
+ rb_define_alloc_func(rb_cRactorLockHash, lockhash_alloc);
391
+ rb_define_method(rb_cRactorLockHash, "initialize", lockhash_initialize, -1);
392
+
393
+ rb_define_method(rb_cRactorLockHash, "[]", lockhash_aref, 1);
394
+ rb_define_method(rb_cRactorLockHash, "fetch", lockhash_fetch, -1);
395
+ rb_define_method(rb_cRactorLockHash, "key?", lockhash_key_p, 1);
396
+ rb_define_method(rb_cRactorLockHash, "keys", lockhash_keys, 0);
397
+ rb_define_method(rb_cRactorLockHash, "to_h", lockhash_to_h, 0);
398
+
399
+ rb_define_method(rb_cRactorLockHash, "[]=", lockhash_aset, 2);
400
+ rb_define_method(rb_cRactorLockHash, "delete", lockhash_delete, 1);
401
+ rb_define_method(rb_cRactorLockHash, "clear", lockhash_clear, 0);
402
+ rb_define_method(rb_cRactorLockHash, "synchronize", lockhash_synchronize, 0);
403
+
404
+ rb_define_method(rb_cRactorLockHash, "inspect", lockhash_inspect, 0);
405
+
406
+ rb_define_const(rb_cRactorLockHash, "NestedLockError", rb_eRactorNestedLock);
407
+ }
@@ -0,0 +1,259 @@
1
+ #include "lock.h"
2
+
3
+ /* Ractor::LockVar - one shareable value, replaced under a lock.
4
+ *
5
+ * It is a variable, not a lock: there is no lock/unlock and no owner query.
6
+ * The lock is only ever held for the duration of #value, #update or
7
+ * #increment, all written here in C so that no interrupt can be delivered
8
+ * between taking the lock and arming the ensure that releases it.
9
+ *
10
+ * #value takes the lock too, so an update's block is atomic as far as readers
11
+ * are concerned, not just its final store. That is what an update block needs
12
+ * if it makes anything else observable:
13
+ *
14
+ * lv.update {|v| $last = v; v + 1 } # readers always see lv.value > $last
15
+ *
16
+ * Touching any LockVar from inside an update is refused, its own included: the
17
+ * block is already given the value, and a nested update's write would be
18
+ * discarded by the outer block's result.
19
+ */
20
+
21
+ static VALUE rb_cRactorLockVar;
22
+ static ID id_plus;
23
+
24
+ struct lockvar {
25
+ VALUE value;
26
+ struct rs_lock lock;
27
+ };
28
+
29
+ static void
30
+ lockvar_mark(void *ptr)
31
+ {
32
+ rb_gc_mark(((struct lockvar *)ptr)->value);
33
+ }
34
+
35
+ static void
36
+ lockvar_free(void *ptr)
37
+ {
38
+ struct lockvar *lv = ptr;
39
+ rs_lock_destroy(&lv->lock);
40
+ ruby_xfree(lv);
41
+ }
42
+
43
+ static size_t
44
+ lockvar_memsize(const void *ptr)
45
+ {
46
+ return sizeof(struct lockvar);
47
+ }
48
+
49
+ static const rb_data_type_t lockvar_data_type = {
50
+ "Ractor::LockVar",
51
+ {lockvar_mark, lockvar_free, lockvar_memsize, NULL},
52
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE
53
+ };
54
+
55
+ static struct lockvar *
56
+ lockvar_ptr(VALUE self)
57
+ {
58
+ struct lockvar *lv;
59
+ TypedData_Get_Struct(self, struct lockvar, &lockvar_data_type, lv);
60
+ return lv;
61
+ }
62
+
63
+ static void
64
+ lockvar_check_shareable(VALUE val)
65
+ {
66
+ if (RB_UNLIKELY(!rb_ractor_shareable_p(val))) {
67
+ rb_raise(rb_eArgError, "only shareable object are allowed");
68
+ }
69
+ }
70
+
71
+ static VALUE
72
+ lockvar_alloc(VALUE klass)
73
+ {
74
+ struct lockvar *lv;
75
+ VALUE obj = TypedData_Make_Struct(klass, struct lockvar, &lockvar_data_type, lv);
76
+ lv->value = Qnil;
77
+ rs_lock_init(&lv->lock);
78
+ return obj;
79
+ }
80
+
81
+ static VALUE
82
+ lockvar_initialize(int argc, VALUE *argv, VALUE self)
83
+ {
84
+ VALUE init = Qnil;
85
+ rb_check_frozen(self); /* send(:initialize) again would write past the lock */
86
+ rb_scan_args(argc, argv, "01", &init);
87
+ lockvar_check_shareable(init);
88
+ lockvar_ptr(self)->value = init;
89
+ /* Only the guarded slot changes, so the LockVar itself can be frozen and
90
+ * shared; RUBY_TYPED_FROZEN_SHAREABLE is what permits it for a T_DATA. */
91
+ rb_obj_freeze(self);
92
+ rb_ractor_make_shareable(self);
93
+ return self;
94
+ }
95
+
96
+ /* --- guarded bodies ------------------------------------------------------- */
97
+
98
+ static VALUE
99
+ lockvar_value_body(VALUE ptr)
100
+ {
101
+ struct rs_guard_arg *arg = (struct rs_guard_arg *)ptr;
102
+ return lockvar_ptr(arg->self)->value;
103
+ }
104
+
105
+ /* yields the value and stores what the block returns */
106
+ static VALUE
107
+ lockvar_update_body(VALUE ptr)
108
+ {
109
+ struct rs_guard_arg *arg = (struct rs_guard_arg *)ptr;
110
+ struct lockvar *lv = lockvar_ptr(arg->self);
111
+ VALUE next = rb_yield(lv->value); /* rs_guarded has marked the lock held */
112
+
113
+ lockvar_check_shareable(next);
114
+ lv->value = next;
115
+ return next;
116
+ }
117
+
118
+ /* adds amount to the value; + may be anything, so the held marker is set too */
119
+ static VALUE
120
+ lockvar_increment_body(VALUE ptr)
121
+ {
122
+ struct rs_guard_arg *arg = (struct rs_guard_arg *)ptr;
123
+ struct lockvar *lv = lockvar_ptr(arg->self);
124
+ VALUE next = rb_funcall(lv->value, id_plus, 1, (VALUE)arg->data);
125
+
126
+ lockvar_check_shareable(next);
127
+ lv->value = next;
128
+ return next;
129
+ }
130
+
131
+ /* --- methods -------------------------------------------------------------- */
132
+
133
+ /*
134
+ * call-seq:
135
+ * lockvar.update {|value| new_value } -> new_value
136
+ *
137
+ * Replaces the value with what the block returns, with nothing else able to
138
+ * read it in between, and returns the new value.
139
+ *
140
+ * Compute the new value from the one the block is given. Reading it outside
141
+ * and using it inside loses whatever landed in between:
142
+ *
143
+ * v = lv.value; lv.update { v + 1 } # wrong: v is stale
144
+ * lv.update { it + 1 } # right
145
+ */
146
+ static VALUE
147
+ lockvar_update(VALUE self)
148
+ {
149
+ rb_need_block();
150
+ return rs_guarded(self, &lockvar_ptr(self)->lock, lockvar_update_body, NULL, false,
151
+ "the inner result would be discarded by the outer block");
152
+ }
153
+
154
+ /*
155
+ * call-seq:
156
+ * lockvar.increment(amount = 1) -> new value
157
+ *
158
+ * Adds +amount+ to the value under the lock and returns the result; the same as
159
+ * <code>update {|v| v + amount }</code>.
160
+ */
161
+ /* Fixnum + Fixnum, when the sum is still a Fixnum: no Ruby runs, so nothing can
162
+ * be interrupted and nothing can re-enter. That lets the whole ceremony go --
163
+ * the held marker, the ensure, the shareable check, the method dispatch -- and
164
+ * leaves the lock and one add. Returns Qundef when it does not apply. */
165
+ static VALUE
166
+ lockvar_fixnum_add(VALUE a, VALUE b)
167
+ {
168
+ long x, y;
169
+
170
+ if (!FIXNUM_P(a) || !FIXNUM_P(b)) return Qundef;
171
+ x = FIX2LONG(a);
172
+ y = FIX2LONG(b);
173
+ if (y > 0 ? x > FIXNUM_MAX - y : x < FIXNUM_MIN - y) return Qundef;
174
+ return LONG2FIX(x + y);
175
+ }
176
+
177
+ static VALUE
178
+ lockvar_increment(int argc, VALUE *argv, VALUE self)
179
+ {
180
+ struct lockvar *lv = lockvar_ptr(self);
181
+ VALUE amount;
182
+
183
+ /* Not rb_scan_args: an explicit nil is an argument, and value + nil raises,
184
+ * where an omitted one means 1. */
185
+ rb_check_arity(argc, 0, 1);
186
+ amount = argc == 0 ? INT2FIX(1) : argv[0];
187
+
188
+ if (FIXNUM_P(amount) && NIL_P(rs_held(rb_thread_current()))) {
189
+ VALUE next;
190
+
191
+ rs_lock_acquire(&lv->lock);
192
+ next = lockvar_fixnum_add(lv->value, amount);
193
+ if (!RB_UNDEF_P(next)) lv->value = next;
194
+ rs_lock_release(&lv->lock);
195
+
196
+ if (!RB_UNDEF_P(next)) return next;
197
+ }
198
+ return rs_guarded(self, &lv->lock, lockvar_increment_body,
199
+ (void *)amount, false, "the block was given that value already");
200
+ }
201
+
202
+ /*
203
+ * call-seq:
204
+ * lockvar.value -> object
205
+ *
206
+ * Reads the value under the lock, so it waits for an update in flight and never
207
+ * sees that update's block half done. Raises NestedLockError if this thread is
208
+ * inside any update.
209
+ *
210
+ * This is a snapshot: true when taken, possibly stale by the time it is used.
211
+ * Never feed it back into #update.
212
+ */
213
+ static VALUE
214
+ lockvar_value(VALUE self)
215
+ {
216
+ struct lockvar *lv = lockvar_ptr(self);
217
+ VALUE v;
218
+
219
+ /* Inside any update, this is the nesting error, and rs_guarded raises it. */
220
+ if (RB_UNLIKELY(!NIL_P(rs_held(rb_thread_current())))) {
221
+ return rs_guarded(self, &lv->lock, lockvar_value_body, NULL, false,
222
+ "the block was given that value already");
223
+ }
224
+
225
+ /* Otherwise: take the lock, read one word, put it back. No Ruby runs in
226
+ * between, so there is no interrupt to guard against and no reason to mark
227
+ * the lock held -- which is what the ensure and the marker are for. */
228
+ rs_lock_acquire(&lv->lock);
229
+ v = lv->value;
230
+ rs_lock_release(&lv->lock);
231
+ return v;
232
+ }
233
+
234
+ /* Reads the value without the lock, on purpose. inspect is for a debugger, a
235
+ * `p`, an exception message: it must not block and must not raise, and a value
236
+ * is one word, so an unlocked read gets some real value, just possibly a stale
237
+ * one. That is the right trade for a display. */
238
+ static VALUE
239
+ lockvar_inspect(VALUE self)
240
+ {
241
+ return rb_sprintf("#<%"PRIsVALUE" %+"PRIsVALUE">",
242
+ rb_obj_class(self), lockvar_ptr(self)->value);
243
+ }
244
+
245
+ void
246
+ Init_lockvar_class(void)
247
+ {
248
+ id_plus = rb_intern("+");
249
+
250
+ rb_cRactorLockVar = rb_define_class_under(rb_cRactor, "LockVar", rb_cObject);
251
+ rb_define_alloc_func(rb_cRactorLockVar, lockvar_alloc);
252
+ rb_define_method(rb_cRactorLockVar, "initialize", lockvar_initialize, -1);
253
+ rb_define_method(rb_cRactorLockVar, "value", lockvar_value, 0);
254
+ rb_define_method(rb_cRactorLockVar, "update", lockvar_update, 0);
255
+ rb_define_method(rb_cRactorLockVar, "increment", lockvar_increment, -1);
256
+ rb_define_method(rb_cRactorLockVar, "inspect", lockvar_inspect, 0);
257
+
258
+ rb_define_const(rb_cRactorLockVar, "NestedLockError", rb_eRactorNestedLock);
259
+ }
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile('ractor/tvar')