Skip to content

Commit 0b0b255

Browse files
committed
allow malloc outside of GVL
We currently do not support calling ruby_xmalloc out of GVL. OTOH oftentimes people do `#define malloc ruby_xmalloc`. Under GVL that has no practical problem but without one it must be a nightmare. Because whether a runtime context has GVL or not is not fixed when the macro is expanded, why don't we actively check that property on the fly and (instead of abnormal end of the process) return NULL. Better safe than sorry I guess.
1 parent af54f0f commit 0b0b255

File tree

3 files changed

+97
-31
lines changed

3 files changed

+97
-31
lines changed

gc.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4208,6 +4208,19 @@ rb_malloc_info_show_results(void)
42084208
void *
42094209
ruby_xmalloc(size_t size)
42104210
{
4211+
if (! ruby_thread_has_gvl_p()) {
4212+
/* if (ruby_native_thread_p()) {
4213+
* // We could reacquire the GVL here using rb_thread_call_with_gvl
4214+
* // but what if that raised an exception? There would be no way
4215+
* // to recover.
4216+
* //
4217+
* // This saves no one from nothing.
4218+
* return rb_thread_call_with_gvl(ruby_xmalloc, (void*)size);
4219+
* }
4220+
*/
4221+
return ruby_mimmalloc(size);
4222+
}
4223+
42114224
if ((ssize_t)size < 0) {
42124225
negative_size_allocation_error("too large allocation size");
42134226
}
@@ -4226,12 +4239,38 @@ ruby_malloc_size_overflow(size_t count, size_t elsize)
42264239
void *
42274240
ruby_xmalloc2(size_t n, size_t size)
42284241
{
4242+
if (! ruby_thread_has_gvl_p()) {
4243+
/* Out of GVL. No GC, and no way to inform the integer overflow. */
4244+
struct rbimpl_size_mul_overflow_tag of =
4245+
rbimpl_size_mul_overflow(n, size);
4246+
4247+
if (of.left) {
4248+
return NULL;
4249+
}
4250+
else {
4251+
return ruby_mimmalloc(of.right);
4252+
}
4253+
}
4254+
42294255
return rb_gc_impl_malloc(rb_gc_get_objspace(), xmalloc2_size(n, size));
42304256
}
42314257

42324258
void *
42334259
ruby_xcalloc(size_t n, size_t size)
42344260
{
4261+
if (! ruby_thread_has_gvl_p()) {
4262+
/* Out of GVL. No GC, and no way to inform the integer overflow. */
4263+
struct rbimpl_size_mul_overflow_tag of =
4264+
rbimpl_size_mul_overflow(n, size);
4265+
4266+
if (of.left) {
4267+
return NULL;
4268+
}
4269+
else {
4270+
return ruby_mimcalloc(of.right, 1);
4271+
}
4272+
}
4273+
42354274
return rb_gc_impl_calloc(rb_gc_get_objspace(), xmalloc2_size(n, size));
42364275
}
42374276

@@ -4241,6 +4280,12 @@ ruby_xcalloc(size_t n, size_t size)
42414280
void *
42424281
ruby_sized_xrealloc(void *ptr, size_t new_size, size_t old_size)
42434282
{
4283+
if (! ruby_thread_has_gvl_p()) {
4284+
/* Out of GVL, no GC. */
4285+
/* Also there is no such thing like ruby_mimrealloc. */
4286+
return realloc(ptr, new_size);
4287+
}
4288+
42444289
if ((ssize_t)new_size < 0) {
42454290
negative_size_allocation_error("too large allocation size");
42464291
}
@@ -4260,6 +4305,24 @@ ruby_xrealloc(void *ptr, size_t new_size)
42604305
void *
42614306
ruby_sized_xrealloc2(void *ptr, size_t n, size_t size, size_t old_n)
42624307
{
4308+
if (! ruby_thread_has_gvl_p()) {
4309+
/* Out of GVL. No GC, and no way to inform the integer overflow. */
4310+
struct rbimpl_size_mul_overflow_tag new_o, old_o;
4311+
4312+
new_o = rbimpl_size_mul_overflow(n, size);
4313+
old_o = rbimpl_size_mul_overflow(old_n, size);
4314+
4315+
if (new_o.left) {
4316+
return NULL;
4317+
}
4318+
else if (old_o.left) {
4319+
return NULL;
4320+
}
4321+
else {
4322+
return realloc(ptr, new_o.right);
4323+
}
4324+
}
4325+
42634326
size_t len = xmalloc2_size(n, size);
42644327
return rb_gc_impl_realloc(rb_gc_get_objspace(), ptr, len, old_n * size);
42654328
}

include/ruby/internal/xmalloc.h

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,23 @@ RBIMPL_SYMBOL_EXPORT_BEGIN()
6161

6262
RBIMPL_ATTR_NODISCARD()
6363
RBIMPL_ATTR_RESTRICT()
64-
RBIMPL_ATTR_RETURNS_NONNULL()
6564
RBIMPL_ATTR_ALLOC_SIZE((1))
6665
/**
67-
* Allocates a storage instance. It is largely the same as system malloc(),
68-
* except:
66+
* Allocates a storage instance. It is largely the same as system malloc().
67+
* Especially when called from outside of GVL this is identical to it, at least
68+
* API-wise. But when under GVL, it exercises some additional manoeuvre
69+
* namely:
6970
*
7071
* - It raises Ruby exceptions instead of returning NULL, and
7172
* - In case of `ENOMEM` it tries to GC to make some room.
7273
*
7374
* @param[in] size Requested amount of memory.
7475
* @exception rb_eNoMemError No space left for `size` bytes allocation.
75-
* @return A valid pointer to an allocated storage instance; which has at
76-
* least `size` bytes width, with appropriate alignment detected by
77-
* the underlying malloc() routine.
78-
* @note It doesn't return NULL.
76+
* @retval NULL Allocation failed but no way to raise anything.
77+
* @retval otherwise A valid pointer to an allocated storage
78+
* instance; which has at least `size` bytes width,
79+
* with appropriate alignment detected by the
80+
* underlying malloc() routine.
7981
* @note Unlike some malloc() implementations, it allocates something and
8082
* returns a meaningful value even when `size` is equal to zero.
8183
* @warning The return value shall be invalidated exactly once by either
@@ -89,7 +91,6 @@ RBIMPL_ATTR_NOEXCEPT(malloc(size))
8991

9092
RBIMPL_ATTR_NODISCARD()
9193
RBIMPL_ATTR_RESTRICT()
92-
RBIMPL_ATTR_RETURNS_NONNULL()
9394
RBIMPL_ATTR_ALLOC_SIZE((1,2))
9495
/**
9596
* Identical to ruby_xmalloc(), except it allocates `nelems` * `elemsiz` bytes.
@@ -102,10 +103,12 @@ RBIMPL_ATTR_ALLOC_SIZE((1,2))
102103
* @param[in] elemsiz Size of an element.
103104
* @exception rb_eNoMemError No space left for allocation.
104105
* @exception rb_eArgError `nelems` * `elemsiz` would overflow.
105-
* @return A valid pointer to an allocated storage instance; which has at
106-
* least `nelems` * `elemsiz` bytes width, with appropriate
107-
* alignment detected by the underlying malloc() routine.
108-
* @note It doesn't return NULL.
106+
* @retval NULL Allocation failed but no way to raise anything.
107+
* @retval otherwise A valid pointer to an allocated storage
108+
* instance; which has at least `nelems` *
109+
* `elemsiz` bytes width, with appropriate
110+
* alignment detected by the underlying malloc()
111+
* routine.
109112
* @note Unlike some malloc() implementations, it allocates something and
110113
* returns a meaningful value even when `nelems` or `elemsiz` or
111114
* both are zero.
@@ -120,7 +123,6 @@ RBIMPL_ATTR_NOEXCEPT(malloc(nelems * elemsiz))
120123

121124
RBIMPL_ATTR_NODISCARD()
122125
RBIMPL_ATTR_RESTRICT()
123-
RBIMPL_ATTR_RETURNS_NONNULL()
124126
RBIMPL_ATTR_ALLOC_SIZE((1,2))
125127
/**
126128
* Identical to ruby_xmalloc2(), except it returns a zero-filled storage
@@ -131,11 +133,13 @@ RBIMPL_ATTR_ALLOC_SIZE((1,2))
131133
* @param[in] elemsiz Size of an element.
132134
* @exception rb_eNoMemError No space left for allocation.
133135
* @exception rb_eArgError `nelems` * `elemsiz` would overflow.
134-
* @return A valid pointer to an allocated storage instance; which has at
135-
* least `nelems` * `elemsiz` bytes width, with appropriate
136-
* alignment detected by the underlying calloc() routine.
136+
* @retval NULL Allocation failed but no way to raise anything.
137+
* @retval otherwise A valid pointer to an allocated storage
138+
* instance; which has at least `nelems` *
139+
* `elemsiz` bytes width, with appropriate
140+
* alignment detected by the underlying calloc()
141+
* routine.
137142
* @post The returned storage instance is filled with zeros.
138-
* @note It doesn't return NULL.
139143
* @note Unlike some calloc() implementations, it allocates something and
140144
* returns a meaningful value even when `nelems` or `elemsiz` or
141145
* both are zero.
@@ -149,7 +153,6 @@ RBIMPL_ATTR_NOEXCEPT(calloc(nelems, elemsiz))
149153
;
150154

151155
RBIMPL_ATTR_NODISCARD()
152-
RBIMPL_ATTR_RETURNS_NONNULL()
153156
RBIMPL_ATTR_ALLOC_SIZE((2))
154157
/**
155158
* Resize the storage instance.
@@ -163,10 +166,11 @@ RBIMPL_ATTR_ALLOC_SIZE((2))
163166
* - ruby_xrealloc2().
164167
* @param[in] newsiz Requested new amount of memory.
165168
* @exception rb_eNoMemError No space left for `newsiz` bytes allocation.
166-
* @return A valid pointer to a (possibly newly allocated) storage
167-
* instance; which has at least `newsiz` bytes width, with
168-
* appropriate alignment detected by the underlying realloc()
169-
* routine.
169+
* @retval NULL Allocation failed but no way to raise anything.
170+
* @retval otherwise A valid pointer to a (possibly newly allocated)
171+
* storage instance; which has at least `newsiz`
172+
* bytes width, with appropriate alignment detected
173+
* by the underlying realloc() routine.
170174
* @pre The passed pointer must point to a valid live storage instance.
171175
* It is a failure to pass an already freed pointer.
172176
* @post In case the function returns the passed pointer as-is, the
@@ -175,7 +179,6 @@ RBIMPL_ATTR_ALLOC_SIZE((2))
175179
* pointer to a newly allocated storage instance is returned. In
176180
* this case `ptr` is invalidated as if it was passed to
177181
* ruby_xfree().
178-
* @note It doesn't return NULL.
179182
* @warning Unlike some realloc() implementations, passing zero to `newsiz`
180183
* is not the same as calling ruby_xfree(), because this function
181184
* never returns NULL. Something meaningful still returns then.
@@ -195,7 +198,6 @@ RBIMPL_ATTR_NOEXCEPT(realloc(ptr, newsiz))
195198
;
196199

197200
RBIMPL_ATTR_NODISCARD()
198-
RBIMPL_ATTR_RETURNS_NONNULL()
199201
RBIMPL_ATTR_ALLOC_SIZE((2,3))
200202
/**
201203
* Identical to ruby_xrealloc(), except it resizes the given storage instance
@@ -219,10 +221,12 @@ RBIMPL_ATTR_ALLOC_SIZE((2,3))
219221
* @param[in] newsiz Requested new size of each element.
220222
* @exception rb_eNoMemError No space left for allocation.
221223
* @exception rb_eArgError `newelems` * `newsiz` would overflow.
222-
* @return A valid pointer to a (possibly newly allocated) storage
223-
* instance; which has at least `newelems` * `newsiz` bytes width,
224-
* with appropriate alignment detected by the underlying realloc()
225-
* routine.
224+
* @retval NULL Allocation failed but no way to raise anything.
225+
* @retval otherwise A valid pointer to a (possibly newly allocated)
226+
* storage instance; which has at least `newelems`
227+
* * `newsiz` bytes width, with appropriate
228+
* alignment detected by the underlying realloc()
229+
* routine.
226230
* @pre The passed pointer must point to a valid live storage instance.
227231
* It is a failure to pass an already freed pointer.
228232
* @post In case the function returns the passed pointer as-is, the
@@ -231,7 +235,6 @@ RBIMPL_ATTR_ALLOC_SIZE((2,3))
231235
* Otherwise a valid pointer to a newly allocated storage instance
232236
* is returned. In this case `ptr` is invalidated as if it was
233237
* passed to ruby_xfree().
234-
* @note It doesn't return NULL.
235238
* @warning Unlike some realloc() implementations, passing zero to either
236239
* `newelems` or `elemsiz` are not the same as calling
237240
* ruby_xfree(), because this function never returns NULL.

internal/gc.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,8 @@ size_t rb_obj_gc_flags(VALUE, ID[], size_t);
245245
void rb_gc_mark_values(long n, const VALUE *values);
246246
void rb_gc_mark_vm_stack_values(long n, const VALUE *values);
247247
void rb_gc_update_values(long n, VALUE *values);
248-
void *ruby_sized_xrealloc(void *ptr, size_t new_size, size_t old_size) RUBY_ATTR_RETURNS_NONNULL RUBY_ATTR_ALLOC_SIZE((2));
249-
void *ruby_sized_xrealloc2(void *ptr, size_t new_count, size_t element_size, size_t old_count) RUBY_ATTR_RETURNS_NONNULL RUBY_ATTR_ALLOC_SIZE((2, 3));
248+
void *ruby_sized_xrealloc(void *ptr, size_t new_size, size_t old_size) RUBY_ATTR_ALLOC_SIZE((2));
249+
void *ruby_sized_xrealloc2(void *ptr, size_t new_count, size_t element_size, size_t old_count) RUBY_ATTR_ALLOC_SIZE((2, 3));
250250
void ruby_sized_xfree(void *x, size_t size);
251251

252252
const char * rb_gc_active_gc_name(void);

0 commit comments

Comments
 (0)