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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/README.md +11 -0
- data/Rakefile +1 -0
- data/ext/RMagick/extconf.rb +3 -2
- data/ext/RMagick/rmagick.h +12 -7
- data/ext/RMagick/rmagick_gvl.cpp +1216 -0
- data/ext/RMagick/rmagick_gvl.h +4 -2
- data/ext/RMagick/rmagick_gvl.hpp +100 -0
- data/ext/RMagick/rmdraw.cpp +101 -70
- data/ext/RMagick/rmfill.cpp +17 -17
- data/ext/RMagick/rmilist.cpp +139 -85
- data/ext/RMagick/rmimage.cpp +1354 -979
- data/ext/RMagick/rminfo.cpp +58 -39
- data/ext/RMagick/rmkinfo.cpp +28 -9
- data/ext/RMagick/rmmain.cpp +2 -0
- data/ext/RMagick/rmmontage.cpp +38 -18
- data/ext/RMagick/rmpixel.cpp +7 -4
- data/ext/RMagick/rmutil.cpp +60 -12
- data/lib/rmagick/version.rb +1 -1
- data/lib/rmagick_internal.rb +15 -4
- data/lib/rvg/rvg.rb +1 -1
- metadata +3 -1
|
@@ -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
|
+
}
|