rmagick 7.1.7 → 7.2.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,1216 @@
1
+ /**
2
+ * Offloading GVL-free calls to a Fiber scheduler.
3
+ *
4
+ * Copyright (c) 2009 - RMagick contributors
5
+ *
6
+ * @file rmagick_gvl.cpp
7
+ */
8
+
9
+ #include "rmagick.h"
10
+ #include "ruby/ractor.h"
11
+ #if defined(RMAGICK_OFFLOAD_SAFE)
12
+ #include <atomic>
13
+ #include <cstddef>
14
+ #if defined(HAVE_WORKING_FORK)
15
+ #include <pthread.h>
16
+ #endif
17
+ #endif
18
+
19
+
20
+ /*
21
+ * rb_thread_call_without_gvl releases the GVL but keeps the calling thread busy
22
+ * until the ImageMagick function returns. Under a Fiber scheduler that has a
23
+ * worker pool (Async with IO::Event::WorkerPool), that stalls every fiber on
24
+ * the thread for the whole operation. Ruby 4.0 lets rb_nogvl hand a call to
25
+ * the scheduler instead (RB_NOGVL_OFFLOAD_SAFE, Feature #20876): the call runs
26
+ * on a worker thread while the calling fiber is suspended.
27
+ *
28
+ * Two things change for the caller when that happens, and rm_gvl_call deals
29
+ * with both:
30
+ *
31
+ * 1. The scheduler can raise into the waiting fiber, for example when the
32
+ * task is stopped or times out. The scheduler waits for the worker to
33
+ * finish first, or cancels the call before a worker starts it, but the
34
+ * exception unwinds through the caller and skips the code after the call.
35
+ * The caller registers what it would have released, and it is released
36
+ * before re-raising. IO::Event::WorkerPool can also cancel a call that no
37
+ * worker has started without raising, when the fiber wakes up early right
38
+ * after a task is stopped, and Ruby then returns as if the call had run.
39
+ * rm_gvl_call offloads such a call once more and otherwise runs it on the
40
+ * calling thread.
41
+ *
42
+ * 2. Other fibers on the same thread run while the call is in flight. While a
43
+ * call reads an object, changing or destroying it raises. While a call
44
+ * changes an image, any use of it raises. Nothing waits, so no fiber is
45
+ * suspended while it holds a pointer that another fiber could free.
46
+ *
47
+ * ImageMagick reads some images through a working area of the pixel cache
48
+ * that every thread outside OpenMP shares, so while an offloaded call uses an
49
+ * image, no other call can use the image or its pixel cache, which clones of
50
+ * the image share. A call that stays on its thread does not mark the image,
51
+ * so an image must not be used by two threads without a scheduler at once.
52
+ * RMagick itself reads and writes pixels through a cache view of its own.
53
+ *
54
+ * The child of a fork keeps none of the workers of the parent, so it drops
55
+ * the marks of every fiber but the one that forked, whose calls go on in the
56
+ * child, and changes back what the dropped calls changed for themselves, such
57
+ * as a channel mask or the links of a list. A dropped call that is resumed
58
+ * there raises instead of running. An image that a call was changing at the
59
+ * fork is in an undefined state in the child.
60
+ *
61
+ * Some cases are not handled. A scheduler must resume a fiber that waits in a
62
+ * call: a fiber collected while it waits leaves its marks behind, and the next
63
+ * fork changes back objects that may be gone. A worker that holds a lock of
64
+ * ImageMagick at the fork can leave the cleanup of a dropped call in the child
65
+ * waiting for good. Draw#annotate changes its text and affine outside
66
+ * rm_gvl_call, so they are changed back only when the call returns.
67
+ *
68
+ * Without a scheduler that offloads, or on Ruby before 4.0, rm_gvl_call runs
69
+ * the function with rb_thread_call_without_gvl. An interrupt that arrives
70
+ * during the function, such as Thread#raise or Timeout, is raised after the
71
+ * function returns, so what the caller registered is released then too.
72
+ * Interrupts are deferred until then because a GC that xmalloc starts in the
73
+ * function (Magick::MANAGED_MEMORY) takes the GVL back and would raise them
74
+ * through ImageMagick. An exception raised by a trap handler there is not
75
+ * deferred.
76
+ */
77
+
78
+ typedef enum
79
+ {
80
+ OffloadRead,
81
+ OffloadUpdate
82
+ } OffloadMode;
83
+
84
+ struct offload_frame;
85
+
86
+ typedef struct
87
+ {
88
+ gvl_function_t *fp;
89
+ void *args;
90
+ void *result;
91
+ struct offload_frame *frame;
92
+ bool started;
93
+ bool done;
94
+ } offload_call_t;
95
+
96
+ #if defined(RMAGICK_OFFLOAD_SAFE)
97
+ static bool frame_dropped(const struct offload_frame *frame);
98
+ #endif
99
+
100
+ // Stores the result here rather than relying on the return value of rb_nogvl
101
+ // or rb_thread_call_without_gvl, which is lost when an exception is raised
102
+ // into the calling fiber after the function returns.
103
+ static void *
104
+ offload_run(void *arg)
105
+ {
106
+ offload_call_t *call = (offload_call_t *)arg;
107
+
108
+ #if defined(RMAGICK_OFFLOAD_SAFE)
109
+ // The fiber of a call that a fork dropped in the child resumed there
110
+ if (call->frame && frame_dropped(call->frame))
111
+ {
112
+ return NULL;
113
+ }
114
+ #endif
115
+ call->started = true;
116
+ call->result = call->fp(call->args);
117
+ call->done = true;
118
+ return call->result;
119
+ }
120
+
121
+ static VALUE
122
+ call_without_gvl(VALUE arg)
123
+ {
124
+ rb_thread_call_without_gvl(offload_run, (void *)arg, RUBY_UBF_PROCESS, NULL);
125
+ return Qnil;
126
+ }
127
+
128
+ static VALUE interrupt_mask = Qnil;
129
+ static ID id_handle_interrupt;
130
+
131
+ static VALUE
132
+ call_without_gvl_block(RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, arg))
133
+ {
134
+ return call_without_gvl(arg);
135
+ }
136
+
137
+ static VALUE
138
+ check_interrupts(VALUE unused)
139
+ {
140
+ rb_thread_check_ints();
141
+ return Qnil;
142
+ }
143
+
144
+ static VALUE
145
+ call_deferring_interrupts(VALUE arg)
146
+ {
147
+ return rb_block_call(rb_cThread, id_handle_interrupt, 1, &interrupt_mask, call_without_gvl_block, arg);
148
+ }
149
+
150
+ #if defined(RMAGICK_OFFLOAD_SAFE)
151
+
152
+ // Value in the table of an object that a call is changing
153
+ #define OFFLOAD_UPDATING ((st_data_t)-1)
154
+
155
+ static void raise_in_use(void) ATTRIBUTE_NORETURN;
156
+
157
+ static rb_ractor_local_key_t offloaded_key;
158
+
159
+ // Marks of the calls in flight in all Ractors, to skip the table when there are none
160
+ static std::atomic<unsigned int> offloads_in_flight(0);
161
+
162
+ // Forks seen by this process; a table from an earlier fork is restored before use
163
+ static unsigned int fork_generation;
164
+
165
+ typedef struct
166
+ {
167
+ void *key;
168
+ OffloadMode mode;
169
+ bool image;
170
+ } offload_mark_t;
171
+
172
+ // A release that changes back an object that the caller passed in
173
+ typedef struct
174
+ {
175
+ void (*release)(void *, intptr_t);
176
+ void *ptr;
177
+ intptr_t arg;
178
+ } offload_restore_t;
179
+
180
+ #define OFFLOAD_MAX_RESTORES 4
181
+
182
+ static size_t
183
+ frame_align(size_t size)
184
+ {
185
+ const size_t align = alignof(std::max_align_t);
186
+
187
+ return (size + align - 1) / align * align;
188
+ }
189
+
190
+ // A call that has marked its objects, with the marks behind it
191
+ typedef struct offload_frame
192
+ {
193
+ offload_mark_t *marks;
194
+ long nmarks;
195
+ offload_restore_t restores[OFFLOAD_MAX_RESTORES];
196
+ int nrestores;
197
+ VALUE thread;
198
+ VALUE fiber;
199
+ bool linked;
200
+ struct offload_frame *prev;
201
+ struct offload_frame *next;
202
+ } offload_frame_t;
203
+
204
+ // Data pointer of an Image, its pixel cache, an Info or a KernelInfo => number
205
+ // of calls in flight that read it, or OFFLOAD_UPDATING. Ruby objects that can
206
+ // be changed never cross Ractors, so each Ractor has its own table.
207
+ typedef struct
208
+ {
209
+ st_table *table;
210
+ unsigned int generation;
211
+ offload_frame_t *frames;
212
+ } offloaded_t;
213
+
214
+ // The entry of the main Ractor, the only one that can fork
215
+ static offloaded_t *main_entry;
216
+
217
+ static void
218
+ offloaded_free(void *ptr)
219
+ {
220
+ offloaded_t *entry = (offloaded_t *)ptr;
221
+
222
+ st_free_table(entry->table);
223
+ xfree(entry);
224
+ }
225
+
226
+ static const struct rb_ractor_local_storage_type offloaded_type = { NULL, offloaded_free };
227
+
228
+ static void
229
+ mark_insert(st_table *table, const void *ptr, OffloadMode mode)
230
+ {
231
+ st_data_t state = 0;
232
+
233
+ st_lookup(table, (st_data_t)ptr, &state);
234
+ st_insert(table, (st_data_t)ptr, mode == OffloadUpdate ? OFFLOAD_UPDATING : state + 1);
235
+ }
236
+
237
+ static void
238
+ mark_remove(st_table *table, const void *ptr)
239
+ {
240
+ st_data_t key = (st_data_t)ptr;
241
+ st_data_t state = 0;
242
+
243
+ st_lookup(table, key, &state);
244
+ if (state == OFFLOAD_UPDATING || state <= 1)
245
+ {
246
+ st_delete(table, &key, NULL);
247
+ }
248
+ else
249
+ {
250
+ st_insert(table, key, state - 1);
251
+ }
252
+ }
253
+
254
+ static offloaded_t *
255
+ offloaded_entry(void)
256
+ {
257
+ offloaded_t *entry = (offloaded_t *)rb_ractor_local_storage_ptr(offloaded_key);
258
+
259
+ if (!entry)
260
+ {
261
+ entry = ALLOC(offloaded_t);
262
+ entry->table = st_init_numtable();
263
+ entry->generation = fork_generation;
264
+ entry->frames = NULL;
265
+ rb_ractor_local_storage_ptr_set(offloaded_key, entry);
266
+ }
267
+ else if (entry->generation != fork_generation)
268
+ {
269
+ st_clear(entry->table);
270
+ entry->generation = fork_generation;
271
+ for (offload_frame_t *frame = entry->frames; frame; frame = frame->next)
272
+ {
273
+ for (long i = 0; i < frame->nmarks; i++)
274
+ {
275
+ mark_insert(entry->table, frame->marks[i].key, frame->marks[i].mode);
276
+ }
277
+ }
278
+ }
279
+ return entry;
280
+ }
281
+
282
+ static st_table *
283
+ offloaded(void)
284
+ {
285
+ return offloaded_entry()->table;
286
+ }
287
+
288
+ static void
289
+ frame_push(offloaded_t *entry, offload_frame_t *frame)
290
+ {
291
+ frame->prev = NULL;
292
+ frame->next = entry->frames;
293
+ if (entry->frames)
294
+ {
295
+ entry->frames->prev = frame;
296
+ }
297
+ entry->frames = frame;
298
+ frame->linked = true;
299
+ }
300
+
301
+ static bool
302
+ frame_dropped(const offload_frame_t *frame)
303
+ {
304
+ return !frame->linked;
305
+ }
306
+
307
+ static void
308
+ frame_unlink(offloaded_t *entry, offload_frame_t *frame)
309
+ {
310
+ if (frame->prev)
311
+ {
312
+ frame->prev->next = frame->next;
313
+ }
314
+ else
315
+ {
316
+ entry->frames = frame->next;
317
+ }
318
+ if (frame->next)
319
+ {
320
+ frame->next->prev = frame->prev;
321
+ }
322
+ frame->linked = false;
323
+ }
324
+
325
+ #if defined(HAVE_WORKING_FORK)
326
+ // The fiber that forks, noted before the fork, and whether it holds the GVL
327
+ static thread_local VALUE fork_fiber;
328
+ static thread_local bool fork_with_gvl;
329
+
330
+ // rb_fiber_current allocates the object of a root fiber on its first call,
331
+ // which a frame on the thread rules out.
332
+ static void
333
+ atfork_prepare(void)
334
+ {
335
+ VALUE thread;
336
+
337
+ fork_fiber = 0;
338
+ fork_with_gvl = ruby_thread_has_gvl_p() && rb_ractor_local_storage_ptr(offloaded_key) == main_entry;
339
+ if (!fork_with_gvl)
340
+ {
341
+ return;
342
+ }
343
+ thread = rb_thread_current();
344
+ for (offload_frame_t *frame = main_entry->frames; frame; frame = frame->next)
345
+ {
346
+ if (frame->thread == thread)
347
+ {
348
+ fork_fiber = rb_fiber_current();
349
+ break;
350
+ }
351
+ }
352
+ }
353
+
354
+ // Runs in the child before Ruby does, so it leaves the table to offloaded_entry.
355
+ // A thread without the GVL, such as a delegate of ImageMagick, forks to exec,
356
+ // and the list may be changing under it. The calls of the other fibers never
357
+ // run in the child, so the objects they changed for the call are changed back
358
+ // while nothing in the child can have destroyed them yet.
359
+ static void
360
+ atfork_child(void)
361
+ {
362
+ unsigned int count = 0;
363
+ offload_frame_t *next;
364
+
365
+ if (!fork_with_gvl)
366
+ {
367
+ return;
368
+ }
369
+ for (offload_frame_t *frame = main_entry->frames; frame; frame = next)
370
+ {
371
+ next = frame->next;
372
+ if (frame->fiber == fork_fiber)
373
+ {
374
+ count += (unsigned int)frame->nmarks;
375
+ }
376
+ else
377
+ {
378
+ for (int i = 0; i < frame->nrestores; i++)
379
+ {
380
+ frame->restores[i].release(frame->restores[i].ptr, frame->restores[i].arg);
381
+ }
382
+ frame_unlink(main_entry, frame);
383
+ }
384
+ }
385
+ offloads_in_flight.store(count, std::memory_order_relaxed);
386
+ fork_generation++;
387
+ }
388
+ #endif
389
+
390
+ static st_data_t
391
+ offload_state(const void *ptr)
392
+ {
393
+ st_data_t state = 0;
394
+
395
+ if (offloads_in_flight.load(std::memory_order_relaxed) == 0)
396
+ {
397
+ return 0;
398
+ }
399
+ st_lookup(offloaded(), (st_data_t)ptr, &state);
400
+ return state;
401
+ }
402
+
403
+ static void
404
+ raise_in_use(void)
405
+ {
406
+ rb_raise(rb_eRuntimeError, "object is in use by another fiber");
407
+ }
408
+
409
+ // The data pointer that identifies obj in the table, or NULL for an object
410
+ // that is not tracked, such as the String of Image.from_blob.
411
+ static void *
412
+ offload_key(VALUE obj)
413
+ {
414
+ if (RB_SPECIAL_CONST_P(obj) || !RB_TYPE_P(obj, T_DATA))
415
+ {
416
+ return NULL;
417
+ }
418
+ return DATA_PTR(obj);
419
+ }
420
+
421
+ static VALUE
422
+ offload_call(VALUE arg)
423
+ {
424
+ rb_nogvl(offload_run, (void *)arg, RUBY_UBF_PROCESS, NULL, RB_NOGVL_OFFLOAD_SAFE);
425
+ return Qnil;
426
+ }
427
+
428
+ static int
429
+ offload_p(void)
430
+ {
431
+ static ID id_blocking_operation_wait = 0;
432
+ VALUE scheduler = rb_fiber_scheduler_current();
433
+
434
+ if (scheduler == Qnil)
435
+ {
436
+ return 0;
437
+ }
438
+ if (!id_blocking_operation_wait)
439
+ {
440
+ id_blocking_operation_wait = rb_intern("blocking_operation_wait");
441
+ }
442
+ return rb_respond_to(scheduler, id_blocking_operation_wait);
443
+ }
444
+ #endif
445
+
446
+
447
+ /**
448
+ * Set up rm_gvl_call. Called once, when the extension is loaded.
449
+ *
450
+ * No Ruby usage (internal function)
451
+ */
452
+ void
453
+ rm_gvl_init(void)
454
+ {
455
+ rb_gc_register_address(&interrupt_mask);
456
+ interrupt_mask = rb_hash_new();
457
+ rb_funcall(interrupt_mask, rb_intern("compare_by_identity"), 0);
458
+ rb_hash_aset(interrupt_mask, rb_cObject, ID2SYM(rb_intern("never")));
459
+ rb_ractor_make_shareable(interrupt_mask);
460
+ id_handle_interrupt = rb_intern("handle_interrupt");
461
+
462
+ #if defined(RMAGICK_OFFLOAD_SAFE)
463
+ offloaded_key = rb_ractor_local_storage_ptr_newkey(&offloaded_type);
464
+ main_entry = offloaded_entry();
465
+ #if defined(HAVE_WORKING_FORK)
466
+ int err = pthread_atfork(atfork_prepare, NULL, atfork_child);
467
+
468
+ if (err)
469
+ {
470
+ rb_syserr_fail(err, "pthread_atfork");
471
+ }
472
+ #endif
473
+ #endif
474
+ }
475
+
476
+
477
+ /**
478
+ * Raise if an offloaded call is changing the object.
479
+ *
480
+ * No Ruby usage (internal function)
481
+ *
482
+ * @param ptr the data pointer of an Image, Info or KernelInfo
483
+ */
484
+ void
485
+ rm_gvl_check_readable(const void *ptr)
486
+ {
487
+ #if defined(RMAGICK_OFFLOAD_SAFE)
488
+ if (offload_state(ptr) == OFFLOAD_UPDATING)
489
+ {
490
+ raise_in_use();
491
+ }
492
+ #endif
493
+ }
494
+
495
+
496
+ /**
497
+ * Whether an offloaded call is using the object.
498
+ *
499
+ * No Ruby usage (internal function)
500
+ *
501
+ * @param ptr the data pointer of an Image, Info or KernelInfo
502
+ * @return true if a call reads or changes the object
503
+ */
504
+ bool
505
+ rm_gvl_in_use(const void *ptr)
506
+ {
507
+ #if defined(RMAGICK_OFFLOAD_SAFE)
508
+ return offload_state(ptr) != 0;
509
+ #else
510
+ return false;
511
+ #endif
512
+ }
513
+
514
+
515
+ /**
516
+ * Raise if an offloaded call is using the object.
517
+ *
518
+ * No Ruby usage (internal function)
519
+ *
520
+ * @param ptr the data pointer of an Image, Info or KernelInfo
521
+ */
522
+ void
523
+ rm_gvl_check_writable(const void *ptr)
524
+ {
525
+ #if defined(RMAGICK_OFFLOAD_SAFE)
526
+ if (rm_gvl_in_use(ptr))
527
+ {
528
+ raise_in_use();
529
+ }
530
+ #endif
531
+ }
532
+
533
+
534
+ #if defined(RMAGICK_OFFLOAD_SAFE)
535
+ static bool
536
+ marked(const offload_mark_t *marks, long count, const void *key)
537
+ {
538
+ for (long i = 0; i < count; i++)
539
+ {
540
+ if (marks[i].key == key)
541
+ {
542
+ return true;
543
+ }
544
+ }
545
+ return false;
546
+ }
547
+
548
+ // The data pointer of an object registered with read() or update(), or of the
549
+ // jth element of an array registered with read_each() or update_each(), and
550
+ // whether the object is an Image
551
+ static void *
552
+ object_key(VALUE obj, const void *ptr, bool each, long j, bool *image)
553
+ {
554
+ if (ptr)
555
+ {
556
+ *image = true;
557
+ return (void *)ptr;
558
+ }
559
+ obj = each ? rb_ary_entry(obj, j) : obj;
560
+ *image = rb_typeddata_is_kind_of(obj, &rm_image_data_type);
561
+ return offload_key(obj);
562
+ }
563
+
564
+ static void
565
+ add_mark(offload_mark_t *marks, long *nmarks, void *key, OffloadMode mode, bool image)
566
+ {
567
+ if (!key || marked(marks, *nmarks, key))
568
+ {
569
+ return;
570
+ }
571
+ marks[*nmarks].key = key;
572
+ marks[*nmarks].mode = mode;
573
+ marks[*nmarks].image = image;
574
+ (*nmarks)++;
575
+ }
576
+
577
+ static bool
578
+ mark_in_use(const offload_mark_t *mark)
579
+ {
580
+ st_data_t state = offload_state(mark->key);
581
+
582
+ if (mark->image)
583
+ {
584
+ return state != 0;
585
+ }
586
+ return state == OFFLOAD_UPDATING || (mark->mode == OffloadUpdate && state != 0);
587
+ }
588
+ #endif
589
+
590
+ static void
591
+ release_exception(void *ptr, intptr_t arg ATTRIBUTE_UNUSED)
592
+ {
593
+ DestroyExceptionInfo((ExceptionInfo *)ptr);
594
+ }
595
+
596
+ static void
597
+ destroy_info(void *ptr, intptr_t arg ATTRIBUTE_UNUSED)
598
+ {
599
+ DestroyImageInfo((ImageInfo *)ptr);
600
+ }
601
+
602
+ static void
603
+ destroy_draw_info(void *ptr, intptr_t arg ATTRIBUTE_UNUSED)
604
+ {
605
+ DestroyDrawInfo((DrawInfo *)ptr);
606
+ }
607
+
608
+ static void
609
+ destroy_kernel(void *ptr, intptr_t arg ATTRIBUTE_UNUSED)
610
+ {
611
+ DestroyKernelInfo((KernelInfo *)ptr);
612
+ }
613
+
614
+ static void
615
+ free_ruby_memory(void *ptr, intptr_t arg ATTRIBUTE_UNUSED)
616
+ {
617
+ xfree(ptr);
618
+ }
619
+
620
+ static void
621
+ free_magick_memory(void *ptr, intptr_t arg ATTRIBUTE_UNUSED)
622
+ {
623
+ magick_free(ptr);
624
+ }
625
+
626
+ static void
627
+ destroy_image(void *ptr, intptr_t arg ATTRIBUTE_UNUSED)
628
+ {
629
+ DestroyImageList((Image *)ptr);
630
+ }
631
+
632
+ static void
633
+ split_images(void *ptr, intptr_t arg ATTRIBUTE_UNUSED)
634
+ {
635
+ rm_split((Image *)ptr);
636
+ }
637
+
638
+ #if defined(IMAGEMAGICK_7)
639
+ static void
640
+ restore_channel_mask(void *ptr, intptr_t arg)
641
+ {
642
+ SetPixelChannelMask((Image *)ptr, (ChannelType)arg);
643
+ }
644
+ #endif
645
+
646
+
647
+ /**
648
+ * Prepare a call of fp(args).
649
+ *
650
+ * No Ruby usage (internal function)
651
+ *
652
+ * @param fp the function
653
+ * @param args its argument struct, on the caller's stack
654
+ */
655
+ rm_gvl_call::rm_gvl_call(gvl_function_t *fp, void *args)
656
+ : fp(fp), args(args), nobjects(0), ncleanups(0), result_type(ResultIgnored), keep(false)
657
+ {
658
+ }
659
+
660
+
661
+ rm_gvl_call &
662
+ rm_gvl_call::add_object(VALUE obj, const void *ptr, bool update, bool each)
663
+ {
664
+ if (nobjects == MaxObjects)
665
+ {
666
+ rb_bug("too many objects for an offloaded call");
667
+ }
668
+ objects[nobjects].obj = obj;
669
+ objects[nobjects].ptr = ptr;
670
+ objects[nobjects].update = update;
671
+ objects[nobjects].each = each;
672
+ nobjects++;
673
+ return *this;
674
+ }
675
+
676
+
677
+ /**
678
+ * Call release(ptr, arg) if the call is refused or unwound. Nothing is
679
+ * registered when ptr is NULL.
680
+ *
681
+ * @param release the function
682
+ * @param ptr its first argument
683
+ * @param arg its second argument
684
+ * @return self
685
+ */
686
+ rm_gvl_call &
687
+ rm_gvl_call::cleanup(void (*release)(void *, intptr_t), void *ptr, intptr_t arg)
688
+ {
689
+ return add_cleanup(release, ptr, arg, 0, false);
690
+ }
691
+
692
+
693
+ /**
694
+ * Like cleanup, for a release that changes an object the caller passed in
695
+ * back, rather than freeing what the call made.
696
+ *
697
+ * @param release the function
698
+ * @param ptr its first argument
699
+ * @param arg its second argument
700
+ * @param size if not 0, arg points to a value of this size, which an
701
+ * offloaded call keeps a copy of
702
+ * @return self
703
+ */
704
+ rm_gvl_call &
705
+ rm_gvl_call::restore(void (*release)(void *, intptr_t), void *ptr, intptr_t arg, size_t size)
706
+ {
707
+ return add_cleanup(release, ptr, arg, size, true);
708
+ }
709
+
710
+
711
+ rm_gvl_call &
712
+ rm_gvl_call::add_cleanup(void (*release)(void *, intptr_t), void *ptr, intptr_t arg, size_t size, bool restore)
713
+ {
714
+ if (!ptr)
715
+ {
716
+ return *this;
717
+ }
718
+ if (ncleanups == MaxCleanups)
719
+ {
720
+ rb_bug("too many cleanups for an offloaded call");
721
+ }
722
+ cleanups[ncleanups].release = release;
723
+ cleanups[ncleanups].ptr = ptr;
724
+ cleanups[ncleanups].arg = arg;
725
+ cleanups[ncleanups].size = size;
726
+ cleanups[ncleanups].restore = restore;
727
+ ncleanups++;
728
+ return *this;
729
+ }
730
+
731
+
732
+ /**
733
+ * The call reads the data of obj. Other fibers can still read it, but cannot
734
+ * change or destroy it while the call is in flight. Another call cannot use
735
+ * an image that the call reads.
736
+ *
737
+ * @param obj an Image, Info or KernelInfo
738
+ * @return self
739
+ */
740
+ rm_gvl_call &
741
+ rm_gvl_call::read(VALUE obj)
742
+ {
743
+ return add_object(obj, NULL, false, false);
744
+ }
745
+
746
+
747
+ /**
748
+ * Like read(VALUE), for the data pointer of an Image that the caller has
749
+ * fetched.
750
+ *
751
+ * @param ptr the data pointer
752
+ * @return self
753
+ */
754
+ rm_gvl_call &
755
+ rm_gvl_call::read(const void *ptr)
756
+ {
757
+ return add_object(Qundef, ptr, false, false);
758
+ }
759
+
760
+
761
+ /**
762
+ * The call changes the data of obj, or the caller replaces it with the result.
763
+ * Other fibers cannot use it while the call is in flight.
764
+ *
765
+ * @param obj an Image, Info or KernelInfo
766
+ * @return self
767
+ */
768
+ rm_gvl_call &
769
+ rm_gvl_call::update(VALUE obj)
770
+ {
771
+ return add_object(obj, NULL, true, false);
772
+ }
773
+
774
+
775
+ /**
776
+ * Like update(VALUE), for the data pointer of an Image.
777
+ *
778
+ * @param ptr the data pointer
779
+ * @return self
780
+ */
781
+ rm_gvl_call &
782
+ rm_gvl_call::update(const void *ptr)
783
+ {
784
+ return add_object(Qundef, ptr, true, false);
785
+ }
786
+
787
+
788
+ /**
789
+ * The call reads every object in the array, such as the images of an ImageList.
790
+ * A copy of the array keeps the objects alive while the call is in flight,
791
+ * even if another fiber removes them from the array.
792
+ *
793
+ * @param ary the array
794
+ * @return self
795
+ */
796
+ rm_gvl_call &
797
+ rm_gvl_call::read_each(VALUE ary)
798
+ {
799
+ return add_object(rb_ary_dup(ary), NULL, false, true);
800
+ }
801
+
802
+
803
+ /**
804
+ * The call changes every object in the array, such as the images of an
805
+ * ImageList. Like read_each(), a copy of the array keeps the objects alive.
806
+ *
807
+ * @param ary the array
808
+ * @return self
809
+ */
810
+ rm_gvl_call &
811
+ rm_gvl_call::update_each(VALUE ary)
812
+ {
813
+ return add_object(rb_ary_dup(ary), NULL, true, true);
814
+ }
815
+
816
+
817
+ /**
818
+ * Destroy the exception if the call is refused or unwound.
819
+ *
820
+ * @param exception the ExceptionInfo, may be NULL
821
+ * @return self
822
+ */
823
+ rm_gvl_call &
824
+ rm_gvl_call::release(ExceptionInfo *exception)
825
+ {
826
+ return cleanup(release_exception, exception, 0);
827
+ }
828
+
829
+
830
+ /**
831
+ * Destroy the ImageInfo if the call is refused or unwound.
832
+ *
833
+ * @param info the ImageInfo, may be NULL
834
+ * @return self
835
+ */
836
+ rm_gvl_call &
837
+ rm_gvl_call::release(ImageInfo *info)
838
+ {
839
+ return cleanup(destroy_info, info, 0);
840
+ }
841
+
842
+
843
+ /**
844
+ * Destroy the DrawInfo if the call is refused or unwound.
845
+ *
846
+ * @param draw_info the DrawInfo, may be NULL
847
+ * @return self
848
+ */
849
+ rm_gvl_call &
850
+ rm_gvl_call::release(DrawInfo *draw_info)
851
+ {
852
+ return cleanup(destroy_draw_info, draw_info, 0);
853
+ }
854
+
855
+
856
+ /**
857
+ * Destroy the KernelInfo if the call is refused or unwound.
858
+ *
859
+ * @param kernel the KernelInfo, may be NULL
860
+ * @return self
861
+ */
862
+ rm_gvl_call &
863
+ rm_gvl_call::release(KernelInfo *kernel)
864
+ {
865
+ return cleanup(destroy_kernel, kernel, 0);
866
+ }
867
+
868
+
869
+ /**
870
+ * Free the buffer, allocated with ALLOC_N, if the call is refused or unwound.
871
+ *
872
+ * @param buffer the buffer, may be NULL
873
+ * @return self
874
+ */
875
+ rm_gvl_call &
876
+ rm_gvl_call::free_buffer(void *buffer)
877
+ {
878
+ return cleanup(free_ruby_memory, buffer, 0);
879
+ }
880
+
881
+
882
+ /**
883
+ * Free the memory, allocated by ImageMagick, if the call is refused or unwound.
884
+ *
885
+ * @param memory the memory, may be NULL
886
+ * @return self
887
+ */
888
+ rm_gvl_call &
889
+ rm_gvl_call::relinquish(void *memory)
890
+ {
891
+ return cleanup(free_magick_memory, memory, 0);
892
+ }
893
+
894
+
895
+ /**
896
+ * Destroy the image if the call is refused or unwound.
897
+ *
898
+ * @param image the image, may be NULL
899
+ * @return self
900
+ */
901
+ rm_gvl_call &
902
+ rm_gvl_call::destroy(Image *image)
903
+ {
904
+ return cleanup(destroy_image, image, 0);
905
+ }
906
+
907
+
908
+ /**
909
+ * Split the linked images if the call is refused or unwound.
910
+ *
911
+ * @param images the first image of the list
912
+ * @return self
913
+ */
914
+ rm_gvl_call &
915
+ rm_gvl_call::split(Image *images)
916
+ {
917
+ return restore(split_images, images, 0);
918
+ }
919
+
920
+
921
+ #if defined(IMAGEMAGICK_7)
922
+ /**
923
+ * Restore the channel mask of the image if the call is refused or unwound.
924
+ *
925
+ * @param image the image whose channel mask was changed
926
+ * @param channel_mask its channel mask before the change
927
+ * @return self
928
+ */
929
+ rm_gvl_call &
930
+ rm_gvl_call::restore_mask(Image *image, ChannelType channel_mask)
931
+ {
932
+ return restore(restore_channel_mask, image, (intptr_t)channel_mask);
933
+ }
934
+ #endif
935
+
936
+
937
+ /**
938
+ * The result is memory that the caller frees with magick_free. It is freed if
939
+ * the call is unwound.
940
+ *
941
+ * @return self
942
+ */
943
+ rm_gvl_call &
944
+ rm_gvl_call::free_result()
945
+ {
946
+ result_type = ResultMemory;
947
+ return *this;
948
+ }
949
+
950
+
951
+ /**
952
+ * Run the call on the calling thread even under a Fiber scheduler, for
953
+ * example because it uses a FILE * that another fiber could close.
954
+ *
955
+ * @param keep whether to keep the call on the calling thread
956
+ * @return self
957
+ */
958
+ rm_gvl_call &
959
+ rm_gvl_call::keep_thread(bool keep)
960
+ {
961
+ this->keep = keep;
962
+ return *this;
963
+ }
964
+
965
+
966
+ /**
967
+ * Release what the caller registered, and the result.
968
+ *
969
+ * No Ruby usage (internal function)
970
+ *
971
+ * @param type how to free the result
972
+ * @param result the result, or NULL
973
+ * @param abandoned whether the child of a fork dropped the call, which
974
+ * changed back the objects that the caller passed in at the fork
975
+ */
976
+ void
977
+ rm_gvl_call::unwind(ResultType type, void *result, bool abandoned)
978
+ {
979
+ if (result)
980
+ {
981
+ if (type == ResultImage)
982
+ {
983
+ DestroyImageList((Image *)result);
984
+ }
985
+ else if (type == ResultMemory)
986
+ {
987
+ magick_free(result);
988
+ }
989
+ }
990
+ for (int i = 0; i < ncleanups; i++)
991
+ {
992
+ if (cleanups[i].restore && abandoned)
993
+ {
994
+ continue;
995
+ }
996
+ cleanups[i].release(cleanups[i].ptr, cleanups[i].arg);
997
+ }
998
+ }
999
+
1000
+
1001
+ void *
1002
+ rm_gvl_call::call(ResultType type)
1003
+ {
1004
+ #if defined(RMAGICK_OFFLOAD_SAFE)
1005
+ bool offload = !keep && offload_p();
1006
+
1007
+ if (offload || offloads_in_flight.load(std::memory_order_relaxed) != 0)
1008
+ {
1009
+ offload_call_t call = { fp, args, NULL, NULL, false, false };
1010
+ VALUE fiber = offload ? rb_fiber_current() : 0;
1011
+ offload_frame_t *frame = NULL;
1012
+ offloaded_t *entry;
1013
+ offload_mark_t *marks;
1014
+ VALUE marks_buffer = 0;
1015
+ long count = 0, nmarks = 0;
1016
+ size_t marks_size, values_size = 0;
1017
+ bool dropped;
1018
+ int tag;
1019
+
1020
+ for (int i = 0; i < nobjects; i++)
1021
+ {
1022
+ count += objects[i].each ? RARRAY_LEN(objects[i].obj) : 1;
1023
+ }
1024
+ marks_size = frame_align(2 * count * sizeof(offload_mark_t));
1025
+ if (offload)
1026
+ {
1027
+ for (int i = 0; i < ncleanups; i++)
1028
+ {
1029
+ values_size += frame_align(cleanups[i].size);
1030
+ }
1031
+ frame = (offload_frame_t *)xmalloc(frame_align(sizeof(offload_frame_t)) + marks_size + values_size);
1032
+ marks = (offload_mark_t *)((char *)frame + frame_align(sizeof(offload_frame_t)));
1033
+ }
1034
+ else
1035
+ {
1036
+ marks = ALLOCV_N(offload_mark_t, marks_buffer, 2 * count);
1037
+ }
1038
+
1039
+ // An object that the call both reads and changes is marked as changed.
1040
+ for (int pass = 0; pass < 2; pass++)
1041
+ {
1042
+ for (int i = 0; i < nobjects; i++)
1043
+ {
1044
+ long len = objects[i].each ? RARRAY_LEN(objects[i].obj) : 1;
1045
+ OffloadMode mode = objects[i].update ? OffloadUpdate : OffloadRead;
1046
+
1047
+ if (objects[i].update != (pass == 0))
1048
+ {
1049
+ continue;
1050
+ }
1051
+ for (long j = 0; j < len; j++)
1052
+ {
1053
+ bool image;
1054
+ void *key = object_key(objects[i].obj, objects[i].ptr, objects[i].each, j, &image);
1055
+
1056
+ add_mark(marks, &nmarks, key, mode, image);
1057
+ if (key && image)
1058
+ {
1059
+ add_mark(marks, &nmarks, ((Image *)key)->cache, mode, true);
1060
+ }
1061
+ }
1062
+ }
1063
+ }
1064
+
1065
+ for (long i = 0; i < nmarks; i++)
1066
+ {
1067
+ if ((offload || marks[i].image) && mark_in_use(&marks[i]))
1068
+ {
1069
+ if (frame)
1070
+ {
1071
+ xfree(frame);
1072
+ }
1073
+ else
1074
+ {
1075
+ ALLOCV_END(marks_buffer);
1076
+ }
1077
+ unwind(type, NULL);
1078
+ raise_in_use();
1079
+ }
1080
+ }
1081
+ if (!offload)
1082
+ {
1083
+ ALLOCV_END(marks_buffer);
1084
+ return call_here(type);
1085
+ }
1086
+
1087
+ // The frame keeps a copy of the values to change back, which can be on
1088
+ // the stack of a fiber that is gone by the next fork.
1089
+ static_assert(MaxCleanups <= OFFLOAD_MAX_RESTORES, "a frame holds every cleanup that restores");
1090
+ char *values = (char *)marks + marks_size;
1091
+
1092
+ frame->marks = marks;
1093
+ frame->nmarks = nmarks;
1094
+ frame->nrestores = 0;
1095
+ for (int i = 0; i < ncleanups; i++)
1096
+ {
1097
+ if (cleanups[i].restore)
1098
+ {
1099
+ offload_restore_t restore = { cleanups[i].release, cleanups[i].ptr, cleanups[i].arg };
1100
+
1101
+ if (cleanups[i].size)
1102
+ {
1103
+ memcpy(values, (const void *)cleanups[i].arg, cleanups[i].size);
1104
+ restore.arg = (intptr_t)values;
1105
+ values += frame_align(cleanups[i].size);
1106
+ }
1107
+ frame->restores[frame->nrestores++] = restore;
1108
+ }
1109
+ }
1110
+ frame->thread = rb_thread_current();
1111
+ frame->fiber = fiber;
1112
+ call.frame = frame;
1113
+
1114
+ entry = offloaded_entry();
1115
+ for (long i = 0; i < nmarks; i++)
1116
+ {
1117
+ mark_insert(entry->table, frame->marks[i].key, frame->marks[i].mode);
1118
+ }
1119
+ offloads_in_flight.fetch_add((unsigned int)nmarks, std::memory_order_relaxed);
1120
+ frame_push(entry, frame);
1121
+
1122
+ rb_protect(offload_call, (VALUE)&call, &tag);
1123
+ if (!tag && !call.started && frame->linked)
1124
+ {
1125
+ rb_protect(offload_call, (VALUE)&call, &tag);
1126
+ }
1127
+
1128
+ // A frame that a fork dropped in the child has no marks to release
1129
+ dropped = !frame->linked;
1130
+ if (!dropped)
1131
+ {
1132
+ entry = offloaded_entry();
1133
+ for (long i = 0; i < nmarks; i++)
1134
+ {
1135
+ mark_remove(entry->table, frame->marks[i].key);
1136
+ }
1137
+ offloads_in_flight.fetch_sub((unsigned int)nmarks, std::memory_order_relaxed);
1138
+ frame_unlink(entry, frame);
1139
+ }
1140
+ xfree(frame);
1141
+ if (dropped)
1142
+ {
1143
+ // The worker may have finished before the fork
1144
+ unwind(type, call.done ? call.result : NULL, true);
1145
+ if (tag)
1146
+ {
1147
+ rb_jump_tag(tag);
1148
+ }
1149
+ rb_raise(rb_eRuntimeError, "call abandoned in the child of a fork");
1150
+ }
1151
+ if (tag)
1152
+ {
1153
+ // The scheduler raised into this fiber after the worker finished.
1154
+ unwind(type, call.result);
1155
+ rb_jump_tag(tag);
1156
+ }
1157
+ if (!call.started)
1158
+ {
1159
+ return call_here(type);
1160
+ }
1161
+
1162
+ return call.result;
1163
+ }
1164
+ #endif
1165
+
1166
+ return call_here(type);
1167
+ }
1168
+
1169
+
1170
+ void *
1171
+ rm_gvl_call::call_here(ResultType type)
1172
+ {
1173
+ offload_call_t call = { fp, args, NULL, NULL, false, false };
1174
+ int tag;
1175
+
1176
+ rb_protect(check_interrupts, Qnil, &tag);
1177
+ if (tag)
1178
+ {
1179
+ unwind(type, NULL);
1180
+ rb_jump_tag(tag);
1181
+ }
1182
+
1183
+ rb_protect(call_deferring_interrupts, (VALUE)&call, &tag);
1184
+ if (tag)
1185
+ {
1186
+ if (call.done)
1187
+ {
1188
+ unwind(type, call.result);
1189
+ }
1190
+ rb_jump_tag(tag);
1191
+ }
1192
+ return call.result;
1193
+ }
1194
+
1195
+
1196
+ /**
1197
+ * Run the call, letting a Fiber scheduler offload it.
1198
+ *
1199
+ * No Ruby usage (internal function)
1200
+ *
1201
+ * @return the result of the call, cast to T
1202
+ */
1203
+ template <>
1204
+ Image *
1205
+ rm_gvl_call::run<Image *>()
1206
+ {
1207
+ return (Image *)call(ResultImage);
1208
+ }
1209
+
1210
+
1211
+ template <>
1212
+ void
1213
+ rm_gvl_call::run<void>()
1214
+ {
1215
+ call(ResultIgnored);
1216
+ }