Skip to content

Commit cc53acc

Browse files
committed
Make C extension Ractor-safe (xmalloc rmem pages, declare rb_ext_ractor_safe)
1 parent 09c914d commit cc53acc

3 files changed

Lines changed: 41 additions & 31 deletions

File tree

ext/msgpack/rbinit.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424

2525
RUBY_FUNC_EXPORTED void Init_msgpack(void)
2626
{
27+
/*
28+
* MSGPACK_RMEM_RACTOR_SAFE: the C methods no longer touch process-global
29+
* mutable state (the s_rmem / s_stack_rmem page slabs were replaced with
30+
* xmalloc/xfree in rmem.h). Declaring the extension Ractor-safe lets a
31+
* MessagePack::Factory built inside a non-main Ractor call pack/unpack.
32+
*/
33+
rb_ext_ractor_safe(true);
34+
2735
VALUE mMessagePack = rb_define_module("MessagePack");
2836

2937
MessagePack_Buffer_module_init(mMessagePack);

ext/msgpack/rmem.c

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,20 @@
1818

1919
#include "rmem.h"
2020

21+
/*
22+
* MSGPACK_RMEM_RACTOR_SAFE: pages are now served directly by xmalloc/xfree
23+
* (see rmem.h). The process-global page slab is gone, so there is nothing to
24+
* initialize or tear down here. These remain as no-ops to keep the
25+
* static_init / static_destroy call sites and ABI unchanged.
26+
*/
2127
void msgpack_rmem_init(msgpack_rmem_t* pm)
2228
{
2329
memset(pm, 0, sizeof(msgpack_rmem_t));
24-
pm->head.pages = xmalloc(MSGPACK_RMEM_PAGE_SIZE * 32);
25-
pm->head.mask = 0xffffffff; /* all bit is 1 = available */
2630
}
2731

2832
void msgpack_rmem_destroy(msgpack_rmem_t* pm)
2933
{
30-
msgpack_rmem_chunk_t* c = pm->array_first;
31-
msgpack_rmem_chunk_t* cend = pm->array_last;
32-
for(; c != cend; c++) {
33-
xfree(c->pages);
34-
}
35-
xfree(pm->head.pages);
36-
xfree(pm->array_first);
34+
(void)pm;
3735
}
3836

3937
void* _msgpack_rmem_alloc2(msgpack_rmem_t* pm)

ext/msgpack/rmem.h

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -74,34 +74,38 @@ static inline bool _msgpack_rmem_chunk_try_free(msgpack_rmem_chunk_t* c, void* m
7474
return false;
7575
}
7676

77+
void _msgpack_rmem_chunk_free(msgpack_rmem_t* pm, msgpack_rmem_chunk_t* c);
78+
79+
/*
80+
* MSGPACK_RMEM_RACTOR_SAFE: the original implementation recycled fixed-size
81+
* pages from a process-global slab (msgpack_rmem_t s_rmem / s_stack_rmem),
82+
* mutating a shared bitmask with no locking. That made the C extension unsafe
83+
* to call from multiple Ractors (or threads) in parallel, and the page slab
84+
* could not declare rb_ext_ractor_safe(true).
85+
*
86+
* We instead hand out each page via Ruby's xmalloc/xfree. These are safe to
87+
* call from any Ractor's execution context, and freeing during GC sweep on a
88+
* different thread than the allocator (the common case for Unpacker_free's
89+
* RUBY_TYPED_FREE_IMMEDIATELY dfree callback) is correct. Pages are still
90+
* contiguous MSGPACK_RMEM_PAGE_SIZE blocks, so the buffer's intra-page
91+
* sub-allocation bookkeeping (rmem_owner / rmem_last / rmem_end) is unchanged.
92+
*
93+
* Trade-off: we lose the per-slab page-recycling fast path. The system
94+
* allocator (jemalloc in production Ruby) services these 4 KiB allocations from
95+
* per-thread arenas, so the overhead is small and, crucially, identical for
96+
* serial and pooled decode paths.
97+
*/
7798
static inline void* msgpack_rmem_alloc(msgpack_rmem_t* pm)
7899
{
79-
if(_msgpack_rmem_chunk_available(&pm->head)) {
80-
return _msgpack_rmem_chunk_alloc(&pm->head);
81-
}
82-
return _msgpack_rmem_alloc2(pm);
100+
(void)pm;
101+
return xmalloc(MSGPACK_RMEM_PAGE_SIZE);
83102
}
84103

85-
void _msgpack_rmem_chunk_free(msgpack_rmem_t* pm, msgpack_rmem_chunk_t* c);
86-
87104
static inline bool msgpack_rmem_free(msgpack_rmem_t* pm, void* mem)
88105
{
89-
if(_msgpack_rmem_chunk_try_free(&pm->head, mem)) {
90-
return true;
91-
}
92-
93-
/* search from last */
94-
msgpack_rmem_chunk_t* c = pm->array_last - 1;
95-
msgpack_rmem_chunk_t* before_first = pm->array_first - 1;
96-
for(; c != before_first; c--) {
97-
if(_msgpack_rmem_chunk_try_free(c, mem)) {
98-
if(c != pm->array_first && c->mask == 0xffffffff) {
99-
_msgpack_rmem_chunk_free(pm, c);
100-
}
101-
return true;
102-
}
103-
}
104-
return false;
106+
(void)pm;
107+
xfree(mem);
108+
return true;
105109
}
106110

107111

0 commit comments

Comments
 (0)