summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Kanis <lars@greiz-reinsdorf.de>2023-05-08 13:21:33 +0200
committerLars Kanis <lars@greiz-reinsdorf.de>2023-05-08 13:21:33 +0200
commitc6b7139d0394638ef51b451b81dcd2ffefb94160 (patch)
treef1513b0afc5ef798382100a53c3191ff52e66d3e
parentc1e6a14476e210efeac79aa260c2eb2d1d98a580 (diff)
parent938b5d8590361acf871fddb9149fe725f65d781f (diff)
downloadffi-c6b7139d0394638ef51b451b81dcd2ffefb94160.tar.gz
Merge branch 'ractor'
-rw-r--r--.github/workflows/ci.yml4
-rw-r--r--README.md3
-rw-r--r--ext/ffi_c/AbstractMemory.c18
-rw-r--r--ext/ffi_c/ArrayType.c2
-rw-r--r--ext/ffi_c/Buffer.c4
-rw-r--r--ext/ffi_c/DynamicLibrary.c11
-rw-r--r--ext/ffi_c/Function.c234
-rw-r--r--ext/ffi_c/FunctionInfo.c10
-rw-r--r--ext/ffi_c/LastError.c1
-rw-r--r--ext/ffi_c/MappedType.c14
-rw-r--r--ext/ffi_c/MemoryPointer.c3
-rw-r--r--ext/ffi_c/Pointer.c5
-rw-r--r--ext/ffi_c/Struct.c8
-rw-r--r--ext/ffi_c/StructByValue.c2
-rw-r--r--ext/ffi_c/StructLayout.c11
-rw-r--r--ext/ffi_c/Type.c44
-rw-r--r--ext/ffi_c/Variadic.c16
-rw-r--r--ext/ffi_c/compat.h14
-rw-r--r--ext/ffi_c/ffi.c4
-rw-r--r--ext/ffi_c/rbffi.h2
-rw-r--r--ffi.gemspec2
-rw-r--r--lib/ffi/autopointer.rb1
-rw-r--r--lib/ffi/compat.rb43
-rw-r--r--lib/ffi/dynamic_library.rb2
-rw-r--r--lib/ffi/ffi.rb2
-rw-r--r--lib/ffi/function.rb71
-rw-r--r--lib/ffi/library.rb55
-rw-r--r--lib/ffi/platform.rb28
-rw-r--r--lib/ffi/struct_layout.rb2
-rw-r--r--lib/ffi/struct_layout_builder.rb2
-rw-r--r--lib/ffi/types.rb35
-rw-r--r--lib/ffi/variadic.rb27
-rw-r--r--spec/ffi/async_callback_spec.rb23
-rw-r--r--spec/ffi/dynamic_library_spec.rb60
-rw-r--r--spec/ffi/enum_spec.rb38
-rw-r--r--spec/ffi/errno_spec.rb8
-rw-r--r--spec/ffi/fixtures/compile.rb2
-rw-r--r--spec/ffi/function_spec.rb24
-rw-r--r--spec/ffi/function_type_spec.rb2
-rw-r--r--spec/ffi/library_spec.rb100
-rw-r--r--spec/ffi/memorypointer_spec.rb34
-rw-r--r--spec/ffi/platform_spec.rb13
-rw-r--r--spec/ffi/pointer_spec.rb12
-rw-r--r--spec/ffi/rbx/memory_pointer_spec.rb12
-rw-r--r--spec/ffi/spec_helper.rb1
-rw-r--r--spec/ffi/struct_by_ref_spec.rb9
-rw-r--r--spec/ffi/struct_spec.rb64
-rw-r--r--spec/ffi/type_spec.rb35
-rw-r--r--spec/ffi/variadic_spec.rb32
49 files changed, 975 insertions, 174 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 371d372..fc48721 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,12 +42,10 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu, macos, windows ]
- ruby: [ 2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, ruby-head, truffleruby-head, jruby-head ]
+ ruby: [ 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, ruby-head, truffleruby-head, jruby-head ]
exclude:
- os: windows
ruby: truffleruby-head
- - os: windows
- ruby: 2.3 # compilation fails
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v2
diff --git a/README.md b/README.md
index c798627..574881f 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ using Ruby-FFI](https://github.com/ffi/ffi/wiki/why-use-ffi).
* C structs (also nested), enums and global variables
* Callbacks from C to Ruby
* Automatic garbage collection of native memory
+* Usable in Ractor
## Synopsis
@@ -62,7 +63,7 @@ On JRuby and TruffleRuby, there are no requirements to install the FFI gem, and
From rubygems:
[sudo] gem install ffi
-
+
From a Gemfile using git or GitHub
gem 'ffi', github: 'ffi/ffi', submodules: true
diff --git a/ext/ffi_c/AbstractMemory.c b/ext/ffi_c/AbstractMemory.c
index 10ddc28..49da32e 100644
--- a/ext/ffi_c/AbstractMemory.c
+++ b/ext/ffi_c/AbstractMemory.c
@@ -70,7 +70,7 @@ const rb_data_type_t rbffi_abstract_memory_data_type = { /* extern */
},
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
static size_t
@@ -316,6 +316,7 @@ static VALUE
memory_clear(VALUE self)
{
AbstractMemory* ptr = MEMORY(self);
+ checkWrite(ptr);
memset(ptr->address, 0, ptr->size);
return self;
}
@@ -687,6 +688,20 @@ memory_copy_from(VALUE self, VALUE rbsrc, VALUE rblen)
return self;
}
+/*
+ * call-seq:
+ * res.freeze
+ *
+ * Freeze the AbstractMemory object and unset the writable flag.
+ */
+static VALUE
+memory_freeze(VALUE self)
+{
+ AbstractMemory* ptr = MEMORY(self);
+ ptr->flags &= ~MEM_WR;
+ return rb_call_super(0, NULL);
+}
+
AbstractMemory*
rbffi_AbstractMemory_Cast(VALUE obj, const rb_data_type_t *data_type)
{
@@ -1102,6 +1117,7 @@ rbffi_AbstractMemory_Init(VALUE moduleFFI)
rb_define_method(classMemory, "type_size", memory_type_size, 0);
rb_define_method(classMemory, "[]", memory_aref, 1);
rb_define_method(classMemory, "__copy_from__", memory_copy_from, 2);
+ rb_define_method(classMemory, "freeze", memory_freeze, 0 );
id_to_ptr = rb_intern("to_ptr");
id_call = rb_intern("call");
diff --git a/ext/ffi_c/ArrayType.c b/ext/ffi_c/ArrayType.c
index 4bd77fd..b1cbcea 100644
--- a/ext/ffi_c/ArrayType.c
+++ b/ext/ffi_c/ArrayType.c
@@ -50,7 +50,7 @@ const rb_data_type_t rbffi_array_type_data_type = { /* extern */
.parent = &rbffi_type_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
diff --git a/ext/ffi_c/Buffer.c b/ext/ffi_c/Buffer.c
index 0bfcc02..78339f3 100644
--- a/ext/ffi_c/Buffer.c
+++ b/ext/ffi_c/Buffer.c
@@ -67,7 +67,7 @@ static const rb_data_type_t buffer_data_type = {
.parent = &rbffi_abstract_memory_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
static const rb_data_type_t allocated_buffer_data_type = {
@@ -80,7 +80,7 @@ static const rb_data_type_t allocated_buffer_data_type = {
.parent = &buffer_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
diff --git a/ext/ffi_c/DynamicLibrary.c b/ext/ffi_c/DynamicLibrary.c
index bcbc8de..9096e74 100644
--- a/ext/ffi_c/DynamicLibrary.c
+++ b/ext/ffi_c/DynamicLibrary.c
@@ -73,7 +73,7 @@ static const rb_data_type_t rbffi_library_data_type = {
},
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
static const rb_data_type_t library_symbol_data_type = {
@@ -87,7 +87,7 @@ static const rb_data_type_t library_symbol_data_type = {
.parent = &rbffi_pointer_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
static VALUE LibraryClass = Qnil, SymbolClass = Qnil;
@@ -161,7 +161,9 @@ library_initialize(VALUE self, VALUE libname, VALUE libflags)
library->handle = RTLD_DEFAULT;
}
#endif
- rb_iv_set(self, "@name", libname != Qnil ? libname : rb_str_new2("[current process]"));
+ rb_iv_set(self, "@name", libname != Qnil ? rb_str_new_frozen(libname) : rb_str_new2("[current process]"));
+
+ rb_obj_freeze(self);
return self;
}
@@ -277,8 +279,9 @@ symbol_new(VALUE library, void* address, VALUE name)
sym->base.memory.typeSize = 1;
sym->base.memory.flags = MEM_RD | MEM_WR;
RB_OBJ_WRITE(obj, &sym->base.rbParent, library);
- RB_OBJ_WRITE(obj, &sym->name, name);
+ RB_OBJ_WRITE(obj, &sym->name, rb_str_new_frozen(name));
+ rb_obj_freeze(obj);
return obj;
}
diff --git a/ext/ffi_c/Function.c b/ext/ffi_c/Function.c
index 0e68722..7810056 100644
--- a/ext/ffi_c/Function.c
+++ b/ext/ffi_c/Function.c
@@ -42,6 +42,10 @@
#include <ruby.h>
#include <ruby/thread.h>
+#if HAVE_RB_EXT_RACTOR_SAFE
+#include <ruby/ractor.h>
+#endif
+
#include <ffi.h>
#if defined(HAVE_NATIVETHREAD) && !defined(_WIN32)
#include <pthread.h>
@@ -65,6 +69,9 @@
#include "MethodHandle.h"
#include "Function.h"
+#define DEFER_ASYNC_CALLBACK 1
+
+struct async_cb_dispatcher;
typedef struct Function_ {
Pointer base;
FunctionType* info;
@@ -73,6 +80,9 @@ typedef struct Function_ {
Closure* closure;
VALUE rbProc;
VALUE rbFunctionInfo;
+#if defined(DEFER_ASYNC_CALLBACK)
+ struct async_cb_dispatcher *dispatcher;
+#endif
} Function;
static void function_mark(void *data);
@@ -86,9 +96,6 @@ static void* callback_with_gvl(void* data);
static VALUE invoke_callback(VALUE data);
static VALUE save_callback_exception(VALUE data, VALUE exc);
-#define DEFER_ASYNC_CALLBACK 1
-
-
#if defined(DEFER_ASYNC_CALLBACK)
static VALUE async_cb_event(void *);
static VALUE async_cb_call(void *);
@@ -108,15 +115,11 @@ static const rb_data_type_t function_data_type = {
.parent = &rbffi_pointer_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
VALUE rbffi_FunctionClass = Qnil;
-#if defined(DEFER_ASYNC_CALLBACK)
-static VALUE async_cb_thread = Qnil;
-#endif
-
static ID id_call = 0, id_to_native = 0, id_from_native = 0, id_cbtable = 0, id_cb_ref = 0;
struct gvl_callback {
@@ -126,7 +129,10 @@ struct gvl_callback {
bool done;
rbffi_frame_t *frame;
#if defined(DEFER_ASYNC_CALLBACK)
+ struct async_cb_dispatcher *dispatcher;
struct gvl_callback* next;
+
+ /* Signal when the callback has finished and retval is set */
# ifndef _WIN32
pthread_cond_t async_cond;
pthread_mutex_t async_mutex;
@@ -138,16 +144,74 @@ struct gvl_callback {
#if defined(DEFER_ASYNC_CALLBACK)
-static struct gvl_callback* async_cb_list = NULL;
+struct async_cb_dispatcher {
+ /* the Ractor-local dispatcher thread */
+ VALUE thread;
+
+ /* single linked list of pending callbacks */
+ struct gvl_callback* async_cb_list;
+
+ /* Signal new entries in async_cb_list */
# ifndef _WIN32
- static pthread_mutex_t async_cb_mutex = PTHREAD_MUTEX_INITIALIZER;
- static pthread_cond_t async_cb_cond = PTHREAD_COND_INITIALIZER;
+ pthread_mutex_t async_cb_mutex;
+ pthread_cond_t async_cb_cond;
# else
- static HANDLE async_cb_cond;
- static CRITICAL_SECTION async_cb_lock;
+ HANDLE async_cb_cond;
+ CRITICAL_SECTION async_cb_lock;
# endif
-#endif
+};
+
+#if HAVE_RB_EXT_RACTOR_SAFE
+static void
+async_cb_dispatcher_mark(void *ptr)
+{
+ struct async_cb_dispatcher *ctx = (struct async_cb_dispatcher *)ptr;
+ rb_gc_mark(ctx->thread);
+}
+
+static void
+async_cb_dispatcher_free(void *ptr)
+{
+ struct async_cb_dispatcher *ctx = (struct async_cb_dispatcher *)ptr;
+ xfree(ctx);
+}
+
+struct rb_ractor_local_storage_type async_cb_dispatcher_key_type = {
+ async_cb_dispatcher_mark,
+ async_cb_dispatcher_free,
+};
+
+static rb_ractor_local_key_t async_cb_dispatcher_key;
+
+static struct async_cb_dispatcher *
+async_cb_dispatcher_get(void)
+{
+ struct async_cb_dispatcher *ctx = (struct async_cb_dispatcher *)rb_ractor_local_storage_ptr(async_cb_dispatcher_key);
+ return ctx;
+}
+static void
+async_cb_dispatcher_set(struct async_cb_dispatcher *ctx)
+{
+ rb_ractor_local_storage_ptr_set(async_cb_dispatcher_key, ctx);
+}
+#else
+// for ruby 2.x
+static struct async_cb_dispatcher *async_cb_dispatcher = NULL;
+
+static struct async_cb_dispatcher *
+async_cb_dispatcher_get(void)
+{
+ return async_cb_dispatcher;
+}
+
+static void
+async_cb_dispatcher_set(struct async_cb_dispatcher *ctx)
+{
+ async_cb_dispatcher = ctx;
+}
+#endif
+#endif
static VALUE
function_allocate(VALUE klass)
@@ -328,9 +392,7 @@ static void
after_fork_callback(void)
{
/* Ensure that a new dispatcher thread is started in a forked process */
- async_cb_thread = Qnil;
- pthread_mutex_init(&async_cb_mutex, NULL);
- pthread_cond_init(&async_cb_cond, NULL);
+ async_cb_dispatcher_set(NULL);
}
#endif
@@ -360,17 +422,30 @@ function_init(VALUE self, VALUE rbFunctionInfo, VALUE rbProc)
}
#if defined(DEFER_ASYNC_CALLBACK)
- if (async_cb_thread == Qnil) {
+ {
+ struct async_cb_dispatcher *ctx = async_cb_dispatcher_get();
+ if (ctx == NULL) {
+ ctx = (struct async_cb_dispatcher*)ALLOC(struct async_cb_dispatcher);
+ ctx->async_cb_list = NULL;
#if !defined(_WIN32)
- if( pthread_atfork(NULL, NULL, after_fork_callback) ){
- rb_warn("FFI: unable to register fork callback");
- }
+ pthread_mutex_init(&ctx->async_cb_mutex, NULL);
+ pthread_cond_init(&ctx->async_cb_cond, NULL);
+ if( pthread_atfork(NULL, NULL, after_fork_callback) ){
+ rb_warn("FFI: unable to register fork callback");
+ }
+#else
+ InitializeCriticalSection(&ctx->async_cb_lock);
+ ctx->async_cb_cond = CreateEvent(NULL, FALSE, FALSE, NULL);
#endif
+ ctx->thread = rb_thread_create(async_cb_event, ctx);
- async_cb_thread = rb_thread_create(async_cb_event, NULL);
- /* Name thread, for better debugging */
- rb_funcall(async_cb_thread, rb_intern("name="), 1, rb_str_new2("FFI Callback Dispatcher"));
+ /* Name thread, for better debugging */
+ rb_funcall(ctx->thread, rb_intern("name="), 1, rb_str_new2("FFI Callback Dispatcher"));
+
+ async_cb_dispatcher_set(ctx);
+ }
+ fn->dispatcher = ctx;
}
#endif
@@ -417,8 +492,8 @@ static VALUE
function_attach(VALUE self, VALUE module, VALUE name)
{
Function* fn;
- char var[1024];
+ StringValue(name);
TypedData_Get_Struct(self, Function, &function_data_type, fn);
if (fn->info->parameterCount == -1) {
@@ -435,12 +510,6 @@ function_attach(VALUE self, VALUE module, VALUE name)
fn->methodHandle = rbffi_MethodHandle_Alloc(fn->info, fn->base.memory.address);
}
- /*
- * Stash the Function in a module variable so it does not get garbage collected
- */
- snprintf(var, sizeof(var), "@@%s", StringValueCStr(name));
- rb_cv_set(module, var, self);
-
rb_define_singleton_method(module, StringValueCStr(name),
rbffi_MethodHandle_CodeAddress(fn->methodHandle), -1);
@@ -462,6 +531,7 @@ function_set_autorelease(VALUE self, VALUE autorelease)
{
Function* fn;
+ rb_check_frozen(self);
TypedData_Get_Struct(self, Function, &function_data_type, fn);
fn->autorelease = RTEST(autorelease);
@@ -479,6 +549,16 @@ function_autorelease_p(VALUE self)
return fn->autorelease ? Qtrue : Qfalse;
}
+static VALUE
+function_type(VALUE self)
+{
+ Function* fn;
+
+ TypedData_Get_Struct(self, Function, &function_data_type, fn);
+
+ return fn->rbFunctionInfo;
+}
+
/*
* call-seq: free
* @return [self]
@@ -504,6 +584,7 @@ function_release(VALUE self)
static void
callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data)
{
+ Function* fn;
struct gvl_callback cb = { 0 };
cb.closure = (Closure *) user_data;
@@ -511,6 +592,7 @@ callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data)
cb.parameters = parameters;
cb.done = false;
cb.frame = rbffi_frame_current();
+ fn = (Function *) cb.closure->info;
if (cb.frame != NULL) cb.frame->exc = Qnil;
@@ -523,18 +605,19 @@ callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data)
#if defined(DEFER_ASYNC_CALLBACK) && !defined(_WIN32)
} else {
bool empty = false;
+ struct async_cb_dispatcher *ctx = fn->dispatcher;
pthread_mutex_init(&cb.async_mutex, NULL);
pthread_cond_init(&cb.async_cond, NULL);
- /* Now signal the async callback thread */
- pthread_mutex_lock(&async_cb_mutex);
- empty = async_cb_list == NULL;
- cb.next = async_cb_list;
- async_cb_list = &cb;
+ /* Now signal the async callback dispatcher thread */
+ pthread_mutex_lock(&ctx->async_cb_mutex);
+ empty = ctx->async_cb_list == NULL;
+ cb.next = ctx->async_cb_list;
+ ctx->async_cb_list = &cb;
- pthread_cond_signal(&async_cb_cond);
- pthread_mutex_unlock(&async_cb_mutex);
+ pthread_cond_signal(&ctx->async_cb_cond);
+ pthread_mutex_unlock(&ctx->async_cb_mutex);
/* Wait for the thread executing the ruby callback to signal it is done */
pthread_mutex_lock(&cb.async_mutex);
@@ -548,17 +631,18 @@ callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data)
#elif defined(DEFER_ASYNC_CALLBACK) && defined(_WIN32)
} else {
bool empty = false;
+ struct async_cb_dispatcher *ctx = fn->dispatcher;
cb.async_event = CreateEvent(NULL, FALSE, FALSE, NULL);
- /* Now signal the async callback thread */
- EnterCriticalSection(&async_cb_lock);
- empty = async_cb_list == NULL;
- cb.next = async_cb_list;
- async_cb_list = &cb;
- LeaveCriticalSection(&async_cb_lock);
+ /* Now signal the async callback dispatcher thread */
+ EnterCriticalSection(&ctx->async_cb_lock);
+ empty = ctx->async_cb_list == NULL;
+ cb.next = ctx->async_cb_list;
+ ctx->async_cb_list = &cb;
+ LeaveCriticalSection(&ctx->async_cb_lock);
- SetEvent(async_cb_cond);
+ SetEvent(ctx->async_cb_cond);
/* Wait for the thread executing the ruby callback to signal it is done */
WaitForSingleObject(cb.async_event, INFINITE);
@@ -569,6 +653,7 @@ callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data)
#if defined(DEFER_ASYNC_CALLBACK)
struct async_wait {
+ struct async_cb_dispatcher *dispatcher;
void* cb;
bool stop;
};
@@ -577,9 +662,10 @@ static void * async_cb_wait(void *);
static void async_cb_stop(void *);
static VALUE
-async_cb_event(void* unused)
+async_cb_event(void* ptr)
{
- struct async_wait w = { 0 };
+ struct async_cb_dispatcher *ctx = (struct async_cb_dispatcher *)ptr;
+ struct async_wait w = { ctx };
w.stop = false;
while (!w.stop) {
@@ -600,23 +686,24 @@ static void *
async_cb_wait(void *data)
{
struct async_wait* w = (struct async_wait *) data;
+ struct async_cb_dispatcher *ctx = w->dispatcher;
w->cb = NULL;
- EnterCriticalSection(&async_cb_lock);
+ EnterCriticalSection(&ctx->async_cb_lock);
- while (!w->stop && async_cb_list == NULL) {
- LeaveCriticalSection(&async_cb_lock);
- WaitForSingleObject(async_cb_cond, INFINITE);
- EnterCriticalSection(&async_cb_lock);
+ while (!w->stop && ctx->async_cb_list == NULL) {
+ LeaveCriticalSection(&ctx->async_cb_lock);
+ WaitForSingleObject(ctx->async_cb_cond, INFINITE);
+ EnterCriticalSection(&ctx->async_cb_lock);
}
- if (async_cb_list != NULL) {
- w->cb = async_cb_list;
- async_cb_list = async_cb_list->next;
+ if (ctx->async_cb_list != NULL) {
+ w->cb = ctx->async_cb_list;
+ ctx->async_cb_list = ctx->async_cb_list->next;
}
- LeaveCriticalSection(&async_cb_lock);
+ LeaveCriticalSection(&ctx->async_cb_lock);
return NULL;
}
@@ -625,11 +712,12 @@ static void
async_cb_stop(void *data)
{
struct async_wait* w = (struct async_wait *) data;
+ struct async_cb_dispatcher *ctx = w->dispatcher;
- EnterCriticalSection(&async_cb_lock);
+ EnterCriticalSection(&ctx->async_cb_lock);
w->stop = true;
- LeaveCriticalSection(&async_cb_lock);
- SetEvent(async_cb_cond);
+ LeaveCriticalSection(&ctx->async_cb_lock);
+ SetEvent(ctx->async_cb_cond);
}
#else
@@ -637,21 +725,22 @@ static void *
async_cb_wait(void *data)
{
struct async_wait* w = (struct async_wait *) data;
+ struct async_cb_dispatcher *ctx = w->dispatcher;
w->cb = NULL;
- pthread_mutex_lock(&async_cb_mutex);
+ pthread_mutex_lock(&ctx->async_cb_mutex);
- while (!w->stop && async_cb_list == NULL) {
- pthread_cond_wait(&async_cb_cond, &async_cb_mutex);
+ while (!w->stop && ctx->async_cb_list == NULL) {
+ pthread_cond_wait(&ctx->async_cb_cond, &ctx->async_cb_mutex);
}
- if (async_cb_list != NULL) {
- w->cb = async_cb_list;
- async_cb_list = async_cb_list->next;
+ if (ctx->async_cb_list != NULL) {
+ w->cb = ctx->async_cb_list;
+ ctx->async_cb_list = ctx->async_cb_list->next;
}
- pthread_mutex_unlock(&async_cb_mutex);
+ pthread_mutex_unlock(&ctx->async_cb_mutex);
return NULL;
}
@@ -660,11 +749,12 @@ static void
async_cb_stop(void *data)
{
struct async_wait* w = (struct async_wait *) data;
+ struct async_cb_dispatcher *ctx = w->dispatcher;
- pthread_mutex_lock(&async_cb_mutex);
+ pthread_mutex_lock(&ctx->async_cb_mutex);
w->stop = true;
- pthread_cond_signal(&async_cb_cond);
- pthread_mutex_unlock(&async_cb_mutex);
+ pthread_cond_signal(&ctx->async_cb_cond);
+ pthread_mutex_unlock(&ctx->async_cb_mutex);
}
#endif
@@ -941,6 +1031,7 @@ rbffi_Function_Init(VALUE moduleFFI)
rb_define_method(rbffi_FunctionClass, "attach", function_attach, 2);
rb_define_method(rbffi_FunctionClass, "free", function_release, 0);
rb_define_method(rbffi_FunctionClass, "autorelease=", function_set_autorelease, 1);
+ rb_define_private_method(rbffi_FunctionClass, "type", function_type, 0);
/*
* call-seq: autorelease
* @return [Boolean]
@@ -960,8 +1051,7 @@ rbffi_Function_Init(VALUE moduleFFI)
id_cb_ref = rb_intern("@__ffi_callback__");
id_to_native = rb_intern("to_native");
id_from_native = rb_intern("from_native");
-#if defined(_WIN32)
- InitializeCriticalSection(&async_cb_lock);
- async_cb_cond = CreateEvent(NULL, FALSE, FALSE, NULL);
+#if defined(DEFER_ASYNC_CALLBACK) && defined(HAVE_RB_EXT_RACTOR_SAFE)
+ async_cb_dispatcher_key = rb_ractor_local_storage_ptr_newkey(&async_cb_dispatcher_key_type);
#endif
}
diff --git a/ext/ffi_c/FunctionInfo.c b/ext/ffi_c/FunctionInfo.c
index 7f77621..b5150d8 100644
--- a/ext/ffi_c/FunctionInfo.c
+++ b/ext/ffi_c/FunctionInfo.c
@@ -67,7 +67,7 @@ const rb_data_type_t rbffi_fntype_data_type = { /* extern */
.parent = &rbffi_type_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
VALUE rbffi_FunctionTypeClass = Qnil;
@@ -251,16 +251,18 @@ fntype_initialize(int argc, VALUE* argv, VALUE self)
fnInfo->invoke = rbffi_GetInvoker(fnInfo);
+ rb_obj_freeze(fnInfo->rbParameterTypes);
+ rb_obj_freeze(self);
return self;
}
/*
- * call-seq: result_type
+ * call-seq: return_type
* @return [Type]
* Get the return type of the function type
*/
static VALUE
-fntype_result_type(VALUE self)
+fntype_return_type(VALUE self)
{
FunctionType* ft;
@@ -311,7 +313,7 @@ rbffi_FunctionInfo_Init(VALUE moduleFFI)
rb_define_alloc_func(rbffi_FunctionTypeClass, fntype_allocate);
rb_define_method(rbffi_FunctionTypeClass, "initialize", fntype_initialize, -1);
- rb_define_method(rbffi_FunctionTypeClass, "result_type", fntype_result_type, 0);
+ rb_define_method(rbffi_FunctionTypeClass, "return_type", fntype_return_type, 0);
rb_define_method(rbffi_FunctionTypeClass, "param_types", fntype_param_types, 0);
}
diff --git a/ext/ffi_c/LastError.c b/ext/ffi_c/LastError.c
index d5ef132..f4da301 100644
--- a/ext/ffi_c/LastError.c
+++ b/ext/ffi_c/LastError.c
@@ -173,7 +173,6 @@ get_last_winapi_error(VALUE self)
static VALUE
set_last_error(VALUE self, VALUE error)
{
-
#ifdef _WIN32
SetLastError(NUM2INT(error));
#else
diff --git a/ext/ffi_c/MappedType.c b/ext/ffi_c/MappedType.c
index b1532e1..2e506f2 100644
--- a/ext/ffi_c/MappedType.c
+++ b/ext/ffi_c/MappedType.c
@@ -57,7 +57,7 @@ static const rb_data_type_t mapped_type_data_type = {
.parent = &rbffi_type_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
@@ -110,6 +110,8 @@ mapped_initialize(VALUE self, VALUE rbConverter)
TypedData_Get_Struct(m->rbType, Type, &rbffi_type_data_type, m->type);
m->base.ffiType = m->type->ffiType;
+ rb_obj_freeze(self);
+
return self;
}
@@ -175,6 +177,15 @@ mapped_from_native(int argc, VALUE* argv, VALUE self)
return rb_funcall2(m->rbConverter, id_from_native, argc, argv);
}
+static VALUE
+mapped_converter(VALUE self)
+{
+ MappedType*m = NULL;
+ TypedData_Get_Struct(self, MappedType, &mapped_type_data_type, m);
+
+ return m->rbConverter;
+}
+
void
rbffi_MappedType_Init(VALUE moduleFFI)
{
@@ -195,5 +206,6 @@ rbffi_MappedType_Init(VALUE moduleFFI)
rb_define_method(rbffi_MappedTypeClass, "native_type", mapped_native_type, 0);
rb_define_method(rbffi_MappedTypeClass, "to_native", mapped_to_native, -1);
rb_define_method(rbffi_MappedTypeClass, "from_native", mapped_from_native, -1);
+ rb_define_method(rbffi_MappedTypeClass, "converter", mapped_converter, 0);
}
diff --git a/ext/ffi_c/MemoryPointer.c b/ext/ffi_c/MemoryPointer.c
index 8227183..a60168e 100644
--- a/ext/ffi_c/MemoryPointer.c
+++ b/ext/ffi_c/MemoryPointer.c
@@ -64,7 +64,7 @@ static const rb_data_type_t memory_pointer_data_type = {
.parent = &rbffi_pointer_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
static VALUE
@@ -136,6 +136,7 @@ memptr_free(VALUE self)
{
Pointer* ptr;
+ rb_check_frozen(self);
TypedData_Get_Struct(self, Pointer, &memory_pointer_data_type, ptr);
if (ptr->allocated) {
diff --git a/ext/ffi_c/Pointer.c b/ext/ffi_c/Pointer.c
index a6e8eb7..dae853a 100644
--- a/ext/ffi_c/Pointer.c
+++ b/ext/ffi_c/Pointer.c
@@ -33,6 +33,7 @@
#include <ruby.h>
#include "rbffi.h"
#include "rbffi_endian.h"
+#include "compat.h"
#include "AbstractMemory.h"
#include "Pointer.h"
@@ -57,7 +58,7 @@ const rb_data_type_t rbffi_pointer_data_type = { /* extern */
.parent = &rbffi_abstract_memory_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
VALUE
@@ -397,6 +398,7 @@ ptr_free(VALUE self)
{
Pointer* ptr;
+ rb_check_frozen(self);
TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr);
if (ptr->allocated) {
@@ -436,6 +438,7 @@ ptr_autorelease(VALUE self, VALUE autorelease)
{
Pointer* ptr;
+ rb_check_frozen(self);
TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr);
ptr->autorelease = autorelease == Qtrue;
diff --git a/ext/ffi_c/Struct.c b/ext/ffi_c/Struct.c
index 3e4f8d1..0fdba71 100644
--- a/ext/ffi_c/Struct.c
+++ b/ext/ffi_c/Struct.c
@@ -82,7 +82,7 @@ const rb_data_type_t rbffi_struct_data_type = { /* extern */
},
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
VALUE rbffi_StructClass = Qnil;
@@ -379,6 +379,7 @@ struct_aset(VALUE self, VALUE fieldName, VALUE value)
Struct* s;
StructField* f;
+ rb_check_frozen(self);
s = struct_validate(self);
f = struct_field(s, fieldName);
@@ -415,6 +416,7 @@ struct_set_pointer(VALUE self, VALUE pointer)
StructLayout* layout;
AbstractMemory* memory;
+ rb_check_frozen(self);
if (!rb_obj_is_kind_of(pointer, rbffi_AbstractMemoryClass)) {
rb_raise(rb_eTypeError, "wrong argument type %s (expected Pointer or Buffer)",
rb_obj_classname(pointer));
@@ -465,6 +467,7 @@ struct_set_layout(VALUE self, VALUE layout)
Struct* s;
TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, s);
+ rb_check_frozen(self);
if (!rb_obj_is_kind_of(layout, rbffi_StructLayoutClass)) {
rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)",
rb_obj_classname(layout), rb_class2name(rbffi_StructLayoutClass));
@@ -538,7 +541,7 @@ static const rb_data_type_t inline_array_data_type = {
},
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
static VALUE
@@ -678,6 +681,7 @@ inline_array_aset(VALUE self, VALUE rbIndex, VALUE rbValue)
{
InlineArray* array;
+ rb_check_frozen(self);
TypedData_Get_Struct(self, InlineArray, &inline_array_data_type, array);
if (array->op != NULL) {
diff --git a/ext/ffi_c/StructByValue.c b/ext/ffi_c/StructByValue.c
index 94061f3..df03684 100644
--- a/ext/ffi_c/StructByValue.c
+++ b/ext/ffi_c/StructByValue.c
@@ -67,7 +67,7 @@ static const rb_data_type_t sbv_type_data_type = {
.parent = &rbffi_type_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
static VALUE
diff --git a/ext/ffi_c/StructLayout.c b/ext/ffi_c/StructLayout.c
index 548ddb5..a56d48f 100644
--- a/ext/ffi_c/StructLayout.c
+++ b/ext/ffi_c/StructLayout.c
@@ -77,7 +77,7 @@ const rb_data_type_t rbffi_struct_layout_data_type = { /* extern */
.parent = &rbffi_type_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
const rb_data_type_t rbffi_struct_field_data_type = { /* extern */
@@ -91,7 +91,7 @@ const rb_data_type_t rbffi_struct_field_data_type = { /* extern */
.parent = &rbffi_type_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
static VALUE
@@ -181,6 +181,8 @@ struct_field_initialize(int argc, VALUE* argv, VALUE self)
break;
}
+ rb_obj_freeze(self);
+
return self;
}
@@ -538,6 +540,11 @@ struct_layout_initialize(VALUE self, VALUE fields, VALUE size, VALUE align)
rb_raise(rb_eRuntimeError, "Struct size is zero");
}
+ rb_obj_freeze(layout->rbFieldMap);
+ rb_obj_freeze(layout->rbFields);
+ rb_obj_freeze(layout->rbFieldNames);
+ rb_obj_freeze(self);
+
return self;
}
diff --git a/ext/ffi_c/Type.c b/ext/ffi_c/Type.c
index 194c81e..a94c009 100644
--- a/ext/ffi_c/Type.c
+++ b/ext/ffi_c/Type.c
@@ -33,6 +33,9 @@
#include <sys/types.h>
#include <ruby.h>
+#if HAVE_RB_EXT_RACTOR_SAFE
+#include <ruby/ractor.h>
+#endif
#include <ffi.h>
#include "rbffi.h"
#include "compat.h"
@@ -54,6 +57,9 @@ static VALUE classBuiltinType = Qnil;
static VALUE moduleNativeType = Qnil;
static VALUE typeMap = Qnil;
static ID id_type_size = 0, id_size = 0;
+#if HAVE_RB_EXT_RACTOR_SAFE
+static rb_ractor_local_key_t custom_typedefs_key;
+#endif
const rb_data_type_t rbffi_type_data_type = { /* extern */
.wrap_struct_name = "FFI::Type",
@@ -64,7 +70,7 @@ const rb_data_type_t rbffi_type_data_type = { /* extern */
},
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
static const rb_data_type_t builtin_type_data_type = {
@@ -77,7 +83,7 @@ static const rb_data_type_t builtin_type_data_type = {
.parent = &rbffi_type_data_type,
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
static size_t
@@ -122,6 +128,8 @@ type_initialize(VALUE self, VALUE value)
rb_raise(rb_eArgError, "wrong type");
}
+ rb_obj_freeze(self);
+
return self;
}
@@ -186,6 +194,8 @@ builtin_type_new(VALUE klass, int nativeType, ffi_type* ffiType, const char* nam
type->type.nativeType = nativeType;
type->type.ffiType = ffiType;
+ rb_obj_freeze(obj);
+
return obj;
}
@@ -245,6 +255,25 @@ rbffi_type_size(VALUE type)
}
}
+static VALUE
+custom_typedefs(VALUE self)
+{
+#if HAVE_RB_EXT_RACTOR_SAFE
+ VALUE hash = rb_ractor_local_storage_value(custom_typedefs_key);
+ if (hash == Qnil) {
+ hash = rb_hash_new();
+ rb_ractor_local_storage_value_set(custom_typedefs_key, hash);
+ }
+#else
+ static VALUE hash = Qundef;
+ if (hash == Qundef) {
+ rb_global_variable(&hash);
+ hash = rb_hash_new();
+ }
+#endif
+ return hash;
+}
+
VALUE
rbffi_Type_Lookup(VALUE name)
{
@@ -254,6 +283,12 @@ rbffi_Type_Lookup(VALUE name)
* Try looking up directly in the type map
*/
VALUE nType;
+ VALUE cust = custom_typedefs(Qnil);
+
+ if ((nType = rb_hash_lookup(cust, name)) != Qnil && rb_obj_is_kind_of(nType, rbffi_TypeClass)) {
+ return nType;
+ }
+
if ((nType = rb_hash_lookup(typeMap, name)) != Qnil && rb_obj_is_kind_of(nType, rbffi_TypeClass)) {
return nType;
}
@@ -286,6 +321,11 @@ rbffi_Type_Init(VALUE moduleFFI)
id_type_size = rb_intern("type_size");
id_size = rb_intern("size");
+#if HAVE_RB_EXT_RACTOR_SAFE
+ custom_typedefs_key = rb_ractor_local_storage_value_newkey();
+#endif
+ rb_define_module_function(moduleFFI, "custom_typedefs", custom_typedefs, 0);
+
/*
* Document-class: FFI::Type::Builtin
* Class for Built-in types.
diff --git a/ext/ffi_c/Variadic.c b/ext/ffi_c/Variadic.c
index a255969..09d7ce8 100644
--- a/ext/ffi_c/Variadic.c
+++ b/ext/ffi_c/Variadic.c
@@ -36,6 +36,9 @@
#include <stdint.h>
#include <stdbool.h>
#include <ruby.h>
+#if HAVE_RB_EXT_RACTOR_SAFE
+#include <ruby/ractor.h>
+#endif
#include <ffi.h>
#include "rbffi.h"
@@ -81,7 +84,7 @@ static const rb_data_type_t variadic_data_type = {
},
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
// macro to update VALUE references, as to trigger write barriers.
- .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | FFI_RUBY_TYPED_FROZEN_SHAREABLE
};
@@ -180,7 +183,7 @@ variadic_initialize(VALUE self, VALUE rbFunction, VALUE rbParameterTypes, VALUE
/*
* @fixed and @type_map are used by the parameter mangling ruby code
*/
- rb_iv_set(self, "@fixed", fixed);
+ rb_iv_set(self, "@fixed", rb_obj_freeze(fixed));
rb_iv_set(self, "@type_map", rb_hash_aref(options, ID2SYM(rb_intern("type_map"))));
return retval;
@@ -318,6 +321,14 @@ variadic_invoke(VALUE self, VALUE parameterTypes, VALUE parameterValues)
return rbffi_NativeValue_ToRuby(invoker->returnType, invoker->rbReturnType, retval);
}
+static VALUE
+variadic_return_type(VALUE self)
+{
+ VariadicInvoker* invoker;
+
+ TypedData_Get_Struct(self, VariadicInvoker, &variadic_data_type, invoker);
+ return invoker->rbReturnType;
+}
void
rbffi_Variadic_Init(VALUE moduleFFI)
@@ -329,5 +340,6 @@ rbffi_Variadic_Init(VALUE moduleFFI)
rb_define_method(classVariadicInvoker, "initialize", variadic_initialize, 4);
rb_define_method(classVariadicInvoker, "invoke", variadic_invoke, 2);
+ rb_define_method(classVariadicInvoker, "return_type", variadic_return_type, 0);
}
diff --git a/ext/ffi_c/compat.h b/ext/ffi_c/compat.h
index 856836f..a1be55d 100644
--- a/ext/ffi_c/compat.h
+++ b/ext/ffi_c/compat.h
@@ -56,6 +56,8 @@
#endif
+
+/* For compatibility with ruby < 2.7 */
#ifdef HAVE_RB_GC_MARK_MOVABLE
#define ffi_compact_callback(x) .dcompact = (x),
#define ffi_gc_location(x) x = rb_gc_location(x)
@@ -65,4 +67,16 @@
#define ffi_gc_location(x)
#endif
+
+/* For compatibility with ruby < 3.0 */
+#ifndef RUBY_TYPED_FROZEN_SHAREABLE
+#define FFI_RUBY_TYPED_FROZEN_SHAREABLE 0
+#else
+#define FFI_RUBY_TYPED_FROZEN_SHAREABLE RUBY_TYPED_FROZEN_SHAREABLE
+#endif
+
+#ifndef HAVE_RB_EXT_RACTOR_SAFE
+#define rb_ractor_make_shareable(self) rb_obj_freeze(self);
+#endif
+
#endif /* RBFFI_COMPAT_H */
diff --git a/ext/ffi_c/ffi.c b/ext/ffi_c/ffi.c
index 22ea3bf..e297f8a 100644
--- a/ext/ffi_c/ffi.c
+++ b/ext/ffi_c/ffi.c
@@ -60,6 +60,10 @@ static VALUE moduleFFI = Qnil;
void
Init_ffi_c(void)
{
+ #ifdef HAVE_RB_EXT_RACTOR_SAFE
+ rb_ext_ractor_safe(1);
+ #endif
+
/*
* Document-module: FFI
*
diff --git a/ext/ffi_c/rbffi.h b/ext/ffi_c/rbffi.h
index 89b3e32..0e4e91a 100644
--- a/ext/ffi_c/rbffi.h
+++ b/ext/ffi_c/rbffi.h
@@ -39,7 +39,7 @@ extern "C" {
#define MAX_PARAMETERS (32)
extern VALUE rbffi_FFIModule;
-
+
extern void rbffi_Type_Init(VALUE ffiModule);
extern void rbffi_Buffer_Init(VALUE ffiModule);
extern void rbffi_Invoker_Init(VALUE ffiModule);
diff --git a/ffi.gemspec b/ffi.gemspec
index 0e9d86a..71b5931 100644
--- a/ffi.gemspec
+++ b/ffi.gemspec
@@ -34,7 +34,7 @@ Gem::Specification.new do |s|
s.rdoc_options = %w[--exclude=ext/ffi_c/.*\.o$ --exclude=ffi_c\.(bundle|so)$]
s.license = 'BSD-3-Clause'
s.require_paths << 'ext/ffi_c'
- s.required_ruby_version = '>= 2.3'
+ s.required_ruby_version = '>= 2.5'
s.add_development_dependency 'rake', '~> 13.0'
s.add_development_dependency 'rake-compiler', '~> 1.1'
s.add_development_dependency 'rake-compiler-dock', '~> 1.0'
diff --git a/lib/ffi/autopointer.rb b/lib/ffi/autopointer.rb
index 4f44171..bbcb6db 100644
--- a/lib/ffi/autopointer.rb
+++ b/lib/ffi/autopointer.rb
@@ -107,6 +107,7 @@ module FFI
# @return [Boolean] +autorelease+
# Set +autorelease+ property. See {Pointer Autorelease section at Pointer}.
def autorelease=(autorelease)
+ raise FrozenError.new("can't modify frozen #{self.class}") if frozen?
@releaser.autorelease=(autorelease)
end
diff --git a/lib/ffi/compat.rb b/lib/ffi/compat.rb
new file mode 100644
index 0000000..7569013
--- /dev/null
+++ b/lib/ffi/compat.rb
@@ -0,0 +1,43 @@
+#
+# Copyright (C) 2023-2023 Lars Kanis
+#
+# This file is part of ruby-ffi.
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the Ruby FFI project nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+module FFI
+ if defined?(Ractor.make_shareable)
+ # This is for FFI internal use only.
+ def self.make_shareable(obj)
+ Ractor.make_shareable(obj)
+ end
+ else
+ def self.make_shareable(obj)
+ obj.freeze
+ end
+ end
+end
diff --git a/lib/ffi/dynamic_library.rb b/lib/ffi/dynamic_library.rb
index a5469c4..b415033 100644
--- a/lib/ffi/dynamic_library.rb
+++ b/lib/ffi/dynamic_library.rb
@@ -35,7 +35,7 @@ module FFI
SEARCH_PATH << '/opt/homebrew/lib'
end
- SEARCH_PATH_MESSAGE = "Searched in <system library path>, #{SEARCH_PATH.join(', ')}"
+ SEARCH_PATH_MESSAGE = "Searched in <system library path>, #{SEARCH_PATH.join(', ')}".freeze
def self.load_library(name, flags)
if name == FFI::CURRENT_PROCESS
diff --git a/lib/ffi/ffi.rb b/lib/ffi/ffi.rb
index dfffa8c..bc1b2e6 100644
--- a/lib/ffi/ffi.rb
+++ b/lib/ffi/ffi.rb
@@ -28,6 +28,7 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+require 'ffi/compat'
require 'ffi/platform'
require 'ffi/data_converter'
require 'ffi/types'
@@ -45,3 +46,4 @@ require 'ffi/autopointer'
require 'ffi/variadic'
require 'ffi/enum'
require 'ffi/version'
+require 'ffi/function'
diff --git a/lib/ffi/function.rb b/lib/ffi/function.rb
new file mode 100644
index 0000000..ac4daf0
--- /dev/null
+++ b/lib/ffi/function.rb
@@ -0,0 +1,71 @@
+#
+# Copyright (C) 2008-2010 JRuby project
+#
+# This file is part of ruby-ffi.
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the Ruby FFI project nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+module FFI
+ class Function
+ # Only MRI allows function type queries
+ if private_method_defined?(:type)
+ # Retrieve the return type of the function
+ #
+ # This method returns FFI type returned by the function.
+ #
+ # @return [FFI::Type]
+ def return_type
+ type.return_type
+ end
+
+ # Retrieve Array of parameter types
+ #
+ # This method returns an Array of FFI types accepted as function parameters.
+ #
+ # @return [Array<FFI::Type>]
+ def param_types
+ type.param_types
+ end
+ end
+
+ # Stash the Function in a module variable so it can be inspected by attached_functions.
+ # On CRuby it also ensures that it does not get garbage collected.
+ module RegisterAttach
+ def attach(mod, name)
+ funcs = mod.instance_variable_get("@ffi_functions")
+ unless funcs
+ funcs = {}
+ mod.instance_variable_set("@ffi_functions", funcs)
+ end
+ funcs[name.to_sym] = self
+ # Jump to the native attach method of CRuby, JRuby or Tuffleruby
+ super
+ end
+ end
+ private_constant :RegisterAttach
+ prepend RegisterAttach
+ end
+end
diff --git a/lib/ffi/library.rb b/lib/ffi/library.rb
index 92d2143..c23112b 100644
--- a/lib/ffi/library.rb
+++ b/lib/ffi/library.rb
@@ -31,7 +31,7 @@
require 'ffi/dynamic_library'
module FFI
- CURRENT_PROCESS = USE_THIS_PROCESS_AS_LIBRARY = Object.new
+ CURRENT_PROCESS = USE_THIS_PROCESS_AS_LIBRARY = FFI.make_shareable(Object.new)
# @param [#to_s] lib library name
# @return [String] library name formatted for current platform
@@ -209,7 +209,7 @@ module FFI
end
raise LoadError unless function
- invokers << if arg_types.length > 0 && arg_types[arg_types.length - 1] == FFI::NativeType::VARARGS
+ invokers << if arg_types[-1] == FFI::NativeType::VARARGS
VariadicInvoker.new(function, arg_types, find_type(ret_type), options)
else
@@ -281,6 +281,7 @@ module FFI
# Attach C variable +cname+ to this module.
def attach_variable(mname, a1, a2 = nil)
cname, type = a2 ? [ a1, a2 ] : [ mname.to_s, a1 ]
+ mname = mname.to_sym
address = nil
ffi_libraries.each do |lib|
begin
@@ -295,9 +296,10 @@ module FFI
# If it is a global struct, just attach directly to the pointer
s = s = type.new(address) # Assigning twice to suppress unused variable warning
self.module_eval <<-code, __FILE__, __LINE__
- @@ffi_gvar_#{mname} = s
+ @ffi_gsvars = {} unless defined?(@ffi_gsvars)
+ @ffi_gsvars[#{mname.inspect}] = s
def self.#{mname}
- @@ffi_gvar_#{mname}
+ @ffi_gsvars[#{mname.inspect}]
end
code
@@ -309,12 +311,13 @@ module FFI
# Attach to this module as mname/mname=
#
self.module_eval <<-code, __FILE__, __LINE__
- @@ffi_gvar_#{mname} = s
+ @ffi_gvars = {} unless defined?(@ffi_gvars)
+ @ffi_gvars[#{mname.inspect}] = s
def self.#{mname}
- @@ffi_gvar_#{mname}[:gvar]
+ @ffi_gvars[#{mname.inspect}][:gvar]
end
def self.#{mname}=(value)
- @@ffi_gvar_#{mname}[:gvar] = value
+ @ffi_gvars[#{mname.inspect}][:gvar] = value
end
code
@@ -539,5 +542,43 @@ module FFI
end || FFI.find_type(t)
end
+
+ # Retrieve all attached functions and their function signature
+ #
+ # This method returns a Hash of method names of attached functions connected by #attach_function and the corresponding function type.
+ # The function type responds to #return_type and #param_types which return the FFI types of the function signature.
+ #
+ # @return [Hash< Symbol => [FFI::Function, FFI::VariadicInvoker] >]
+ def attached_functions
+ @ffi_functions || {}
+ end
+
+ # Retrieve all attached variables and their type
+ #
+ # This method returns a Hash of variable names and the corresponding type or variables connected by #attach_variable .
+ #
+ # @return [Hash< Symbol => ffi_type >]
+ def attached_variables
+ (
+ (@ffi_gsvars || {}).map do |name, gvar|
+ [name, gvar.class]
+ end +
+ (@ffi_gvars || {}).map do |name, gvar|
+ [name, gvar.layout[:gvar].type]
+ end
+ ).to_h
+ end
+
+ # Freeze all definitions of the module
+ #
+ # This freezes the module's definitions, so that it can be used in a Ractor.
+ # No further methods or variables can be attached and no further enums or typedefs can be created in this module afterwards.
+ def freeze
+ instance_variables.each do |name|
+ var = instance_variable_get(name)
+ FFI.make_shareable(var)
+ end
+ nil
+ end
end
end
diff --git a/lib/ffi/platform.rb b/lib/ffi/platform.rb
index bf01a27..5ac4dd7 100644
--- a/lib/ffi/platform.rb
+++ b/lib/ffi/platform.rb
@@ -29,13 +29,15 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.#
require 'rbconfig'
+require_relative 'compat'
+
module FFI
class PlatformError < LoadError; end
# This module defines different constants and class methods to play with
# various platforms.
module Platform
- OS = case RbConfig::CONFIG['host_os'].downcase
+ OS = FFI.make_shareable(case RbConfig::CONFIG['host_os'].downcase
when /linux/
"linux"
when /darwin/
@@ -54,13 +56,13 @@ module FFI
"windows"
else
RbConfig::CONFIG['host_os'].downcase
- end
+ end)
OSVERSION = RbConfig::CONFIG['host_os'].gsub(/[^\d]/, '').to_i
- CPU = RbConfig::CONFIG['host_cpu']
+ CPU = FFI.make_shareable(RbConfig::CONFIG['host_cpu'])
- ARCH = case CPU.downcase
+ ARCH = FFI.make_shareable(case CPU.downcase
when /amd64|x86_64|x64/
"x86_64"
when /i\d86|x86|i86pc/
@@ -81,7 +83,7 @@ module FFI
end
else
RbConfig::CONFIG['host_cpu']
- end
+ end)
private
# @param [String) os
@@ -105,21 +107,21 @@ module FFI
# Add the version for known ABI breaks
name_version = "12" if IS_FREEBSD && OSVERSION >= 12 # 64-bit inodes
- NAME = "#{ARCH}-#{OS}#{name_version}"
- CONF_DIR = File.join(File.dirname(__FILE__), 'platform', NAME)
+ NAME = FFI.make_shareable("#{ARCH}-#{OS}#{name_version}")
+ CONF_DIR = FFI.make_shareable(File.join(File.dirname(__FILE__), 'platform', NAME))
public
- LIBPREFIX = case OS
+ LIBPREFIX = FFI.make_shareable(case OS
when /windows|msys/
''
when /cygwin/
'cyg'
else
'lib'
- end
+ end)
- LIBSUFFIX = case OS
+ LIBSUFFIX = FFI.make_shareable(case OS
when /darwin/
'dylib'
when /linux|bsd|solaris/
@@ -129,9 +131,9 @@ module FFI
else
# Punt and just assume a sane unix (i.e. anything but AIX)
'so'
- end
+ end)
- LIBC = if IS_WINDOWS
+ LIBC = FFI.make_shareable(if IS_WINDOWS
crtname = RbConfig::CONFIG["RUBY_SO_NAME"][/msvc\w+/] || 'ucrtbase'
"#{crtname}.dll"
elsif IS_GNU
@@ -143,7 +145,7 @@ module FFI
"msys-2.0.dll"
else
"#{LIBPREFIX}c.#{LIBSUFFIX}"
- end
+ end)
LITTLE_ENDIAN = 1234 unless defined?(LITTLE_ENDIAN)
BIG_ENDIAN = 4321 unless defined?(BIG_ENDIAN)
diff --git a/lib/ffi/struct_layout.rb b/lib/ffi/struct_layout.rb
index d5a78a7..3fd68cb 100644
--- a/lib/ffi/struct_layout.rb
+++ b/lib/ffi/struct_layout.rb
@@ -80,8 +80,8 @@ module FFI
class Mapped < Field
def initialize(name, offset, type, orig_field)
- super(name, offset, type)
@orig_field = orig_field
+ super(name, offset, type)
end
def get(ptr)
diff --git a/lib/ffi/struct_layout_builder.rb b/lib/ffi/struct_layout_builder.rb
index 4d6a464..d7d26a2 100644
--- a/lib/ffi/struct_layout_builder.rb
+++ b/lib/ffi/struct_layout_builder.rb
@@ -112,7 +112,7 @@ module FFI
Type::FLOAT64,
Type::LONGDOUBLE,
Type::BOOL,
- ]
+ ].freeze
# @param [String, Symbol] name name of the field
# @param [Array, DataConverter, Struct, StructLayout::Field, Symbol, Type] type type of the field
diff --git a/lib/ffi/types.rb b/lib/ffi/types.rb
index 90f50c1..8f5d897 100644
--- a/lib/ffi/types.rb
+++ b/lib/ffi/types.rb
@@ -33,12 +33,24 @@
# see {file:README}
module FFI
+ unless defined?(self.custom_typedefs)
+ # Truffleruby and JRuby don't support Ractor so far.
+ # So they don't need separation between builtin and custom types.
+ def self.custom_typedefs
+ TypeDefs
+ end
+ writable_typemap = true
+ end
+
# @param [Type, DataConverter, Symbol] old type definition used by {FFI.find_type}
# @param [Symbol] add new type definition's name to add
# @return [Type]
# Add a definition type to type definitions.
+ #
+ # The type definition is local per Ractor.
def self.typedef(old, add)
- TypeDefs[add] = self.find_type(old)
+ tm = custom_typedefs
+ tm[add] = self.find_type(old)
end
# (see FFI.typedef)
@@ -46,6 +58,14 @@ module FFI
typedef old, add
end
+ class << self
+ private def __typedef(old, add)
+ TypeDefs[add] = self.find_type(old)
+ end
+
+ private :custom_typedefs
+ end
+
# @param [Type, DataConverter, Symbol] name
# @param [Hash] type_map if nil, {FFI::TypeDefs} is used
@@ -57,9 +77,12 @@ module FFI
if name.is_a?(Type)
name
- elsif type_map && type_map.has_key?(name)
+ elsif type_map&.has_key?(name)
type_map[name]
+ elsif (tm=custom_typedefs).has_key?(name)
+ tm[name]
+
elsif TypeDefs.has_key?(name)
TypeDefs[name]
@@ -168,7 +191,7 @@ module FFI
end
end
- typedef(StrPtrConverter, :strptr)
+ __typedef(StrPtrConverter, :strptr)
# @param type +type+ is an instance of class accepted by {FFI.find_type}
# @return [Numeric]
@@ -184,11 +207,13 @@ module FFI
f.each_line { |line|
if line.index(prefix) == 0
new_type, orig_type = line.chomp.slice(prefix.length..-1).split(/\s*=\s*/)
- typedef(orig_type.to_sym, new_type.to_sym)
+ __typedef(orig_type.to_sym, new_type.to_sym)
end
}
end
- typedef :pointer, :caddr_t
+ __typedef :pointer, :caddr_t
rescue Errno::ENOENT
end
+
+ FFI.make_shareable(TypeDefs) unless writable_typemap
end
diff --git a/lib/ffi/variadic.rb b/lib/ffi/variadic.rb
index 743ce7f..ee33409 100644
--- a/lib/ffi/variadic.rb
+++ b/lib/ffi/variadic.rb
@@ -54,16 +54,27 @@ module FFI
invoker = self
params = "*args"
call = "call"
- mod.module_eval <<-code
- @@#{mname} = invoker
- def self.#{mname}(#{params})
- @@#{mname}.#{call}(#{params})
- end
- def #{mname}(#{params})
- @@#{mname}.#{call}(#{params})
- end
+ mname = mname.to_sym
+ mod.module_eval <<-code, __FILE__, __LINE__
+ @ffi_functions = {} unless defined?(@ffi_functions)
+ @ffi_functions[#{mname.inspect}] = invoker
+
+ def self.#{mname}(#{params})
+ @ffi_functions[#{mname.inspect}].#{call}(#{params})
+ end
+
+ define_method(#{mname.inspect}, &method(#{mname.inspect}))
code
invoker
end
+
+ # Retrieve Array of parameter types
+ #
+ # This method returns an Array of FFI types accepted as function parameters.
+ #
+ # @return [Array<FFI::Type>]
+ def param_types
+ [*@fixed, Type::Builtin::VARARGS]
+ end
end
end
diff --git a/spec/ffi/async_callback_spec.rb b/spec/ffi/async_callback_spec.rb
index 51a4886..7bb9f70 100644
--- a/spec/ffi/async_callback_spec.rb
+++ b/spec/ffi/async_callback_spec.rb
@@ -50,4 +50,27 @@ describe "async callback" do
expect(callback_runner_thread.name).to eq("FFI Callback Runner")
end
+
+ it "works in Ractor", :ractor do
+ skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
+
+ res = Ractor.new do
+ v = 0xdeadbeef
+ correct_ractor = false
+ correct_thread = false
+ thread = Thread.current
+ rac = Ractor.current
+ cb = Proc.new do |i|
+ v = i
+ correct_ractor = rac == Ractor.current
+ correct_thread = thread != Thread.current
+ end
+ LibTest.testAsyncCallback(cb, 0x7fffffff)
+
+ [v, correct_ractor, correct_thread]
+ end.take
+
+ expect(res).to eq([0x7fffffff, true, true])
+ end
+
end
diff --git a/spec/ffi/dynamic_library_spec.rb b/spec/ffi/dynamic_library_spec.rb
new file mode 100644
index 0000000..088a822
--- /dev/null
+++ b/spec/ffi/dynamic_library_spec.rb
@@ -0,0 +1,60 @@
+#
+# This file is part of ruby-ffi.
+# For licensing, see LICENSE.SPECS
+#
+
+require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
+
+describe FFI::DynamicLibrary do
+ it "should be shareable for Ractor", :ractor do
+ libtest = FFI::DynamicLibrary.open(TestLibrary::PATH,
+ FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL)
+
+ res = Ractor.new(libtest) do |libtest2|
+ libtest2.find_symbol("testClosureVrV").address
+ end.take
+
+ expect( res ).to be > 0
+ end
+
+ it "load a library in a Ractor", :ractor do
+ res = Ractor.new do
+ libtest = FFI::DynamicLibrary.open(TestLibrary::PATH,
+ FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL)
+ libtest.find_symbol("testClosureVrV")
+ end.take
+
+ expect(res.address).to be > 0
+ end
+
+ it "has a memsize function", skip: RUBY_ENGINE != "ruby" do
+ base_size = ObjectSpace.memsize_of(Object.new)
+
+ libtest = FFI::DynamicLibrary.open(TestLibrary::PATH,
+ FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL)
+ size = ObjectSpace.memsize_of(libtest)
+ expect(size).to be > base_size
+ end
+
+ describe Symbol do
+ before do
+ @libtest = FFI::DynamicLibrary.open(
+ TestLibrary::PATH,
+ FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL,
+ )
+ end
+
+ it "has a memsize function", skip: RUBY_ENGINE != "ruby" do
+ base_size = ObjectSpace.memsize_of(Object.new)
+
+ symbol = @libtest.find_symbol("gvar_gstruct_set")
+ size = ObjectSpace.memsize_of(symbol)
+ expect(size).to be > base_size
+ end
+
+ it "should be shareable for Ractor", :ractor do
+ symbol = @libtest.find_symbol("gvar_gstruct_set")
+ expect(Ractor.shareable?(symbol)).to be true
+ end
+ end
+end
diff --git a/spec/ffi/enum_spec.rb b/spec/ffi/enum_spec.rb
index b8c5b57..7a55b07 100644
--- a/spec/ffi/enum_spec.rb
+++ b/spec/ffi/enum_spec.rb
@@ -26,14 +26,18 @@ module TestEnum3
ffi_lib TestLibrary::PATH
enum :enum_type1, [:c1, :c2, :c3, :c4]
- enum :enum_type2, [:c5, 42, :c6, :c7, :c8]
- enum :enum_type3, [:c9, 42, :c10, :c11, 4242, :c12]
- enum :enum_type4, [:c13, 42, :c14, 4242, :c15, 424242, :c16, 42424242]
-
attach_function :test_tagged_typedef_enum1, [:enum_type1], :enum_type1
+
+ enum :enum_type2, [:c5, 42, :c6, :c7, :c8]
attach_function :test_tagged_typedef_enum2, [:enum_type2], :enum_type2
+
+ enum :enum_type3, [:c9, 42, :c10, :c11, 4242, :c12]
attach_function :test_tagged_typedef_enum3, [:enum_type3], :enum_type3
+
+ enum :enum_type4, [:c13, 42, :c14, 4242, :c15, 424242, :c16, 42424242]
attach_function :test_tagged_typedef_enum4, [:enum_type4], :enum_type4
+
+ freeze
end
module TestEnum4
@@ -44,18 +48,22 @@ module TestEnum4
enum :enum_type1, [:c5, 0x42, :c6, :c7, :c8]
enum :enum_type2, [:c9, 0x42, :c10, :c11, 0x4242, :c12]
enum :enum_type3, [:c13, 0x42, :c14, 0x4242, :c15, 0x42424242, :c16, 0x4242424242424242]
- enum FFI::Type::UINT16, :enum_type4, [:c17, 0x42, :c18, :c19, :c20]
- enum FFI::Type::UINT32, :enum_type5, [:c21, 0x42, :c22, :c23, 0x4242, :c24]
- enum FFI::Type::UINT64, :enum_type6, [:c25, 0x42, :c26, 0x4242, :c27, 0x42424242, :c28, 0x4242424242424242]
- enum FFI::Type::UINT64, [:c29, 0x4242424242424242, :c30, :c31, :c32]
attach_function :test_untagged_nonint_enum, [:uint8], :uint8
attach_function :test_tagged_nonint_enum1, [:uint16], :uint16
attach_function :test_tagged_nonint_enum2, [:uint32], :uint32
attach_function :test_tagged_nonint_enum3, [:uint64], :uint64
+
+ enum FFI::Type::UINT16, :enum_type4, [:c17, 0x42, :c18, :c19, :c20]
+ enum FFI::Type::UINT32, :enum_type5, [:c21, 0x42, :c22, :c23, 0x4242, :c24]
+ enum FFI::Type::UINT64, :enum_type6, [:c25, 0x42, :c26, 0x4242, :c27, 0x42424242, :c28, 0x4242424242424242]
+ enum FFI::Type::UINT64, [:c29, 0x4242424242424242, :c30, :c31, :c32]
+
attach_function :test_tagged_nonint_enum4, :test_tagged_nonint_enum1, [:enum_type4], :enum_type4
attach_function :test_tagged_nonint_enum5, :test_tagged_nonint_enum2, [:enum_type5], :enum_type5
attach_function :test_tagged_nonint_enum6, :test_tagged_nonint_enum3, [:enum_type6], :enum_type6
+
+ freeze
end
describe "A library with no enum defined" do
@@ -430,4 +438,18 @@ describe "All enums" do
end
end.to raise_error(ArgumentError, /duplicate/)
end
+
+ it "should be usable in Ractor", :ractor do
+ res = Ractor.new do
+ [
+ TestEnum1.test_untagged_enum(:c1),
+ TestEnum3.test_tagged_typedef_enum1(:c1),
+ TestEnum4.test_tagged_nonint_enum4(0x45),
+ TestEnum3.enum_type(:enum_type1)[0],
+ TestEnum4.enum_type(:enum_type6)[0x4242424242424242],
+ TestEnum4.enum_value(:c3)
+ ]
+ end.take
+ expect( res ).to eq( [0, :c1, :c20, :c1, :c28, 2] )
+ end
end
diff --git a/spec/ffi/errno_spec.rb b/spec/ffi/errno_spec.rb
index 4170f92..c0207b8 100644
--- a/spec/ffi/errno_spec.rb
+++ b/spec/ffi/errno_spec.rb
@@ -34,4 +34,12 @@ describe "FFI.errno" do
expect(FFI.errno).to eq(0x12345678)
end
end
+
+ it "works in Ractor", :ractor do
+ res = Ractor.new do
+ LibTest.setLastError(0x12345678)
+ FFI.errno
+ end.take
+ expect(res).to eq(0x12345678)
+ end
end
diff --git a/spec/ffi/fixtures/compile.rb b/spec/ffi/fixtures/compile.rb
index 58ee561..2be97cb 100644
--- a/spec/ffi/fixtures/compile.rb
+++ b/spec/ffi/fixtures/compile.rb
@@ -69,5 +69,5 @@ module TestLibrary
lib
end
- PATH = compile_library(".", "libtest.#{FFI::Platform::LIBSUFFIX}")
+ PATH = FFI.make_shareable(compile_library(".", "libtest.#{FFI::Platform::LIBSUFFIX}"))
end
diff --git a/spec/ffi/function_spec.rb b/spec/ffi/function_spec.rb
index 7f90ea3..77d09d8 100644
--- a/spec/ffi/function_spec.rb
+++ b/spec/ffi/function_spec.rb
@@ -48,6 +48,30 @@ describe FFI::Function do
expect(LibTest.testFunctionAdd(10, 10, function_add)).to eq(20)
end
+ def adder(a, b)
+ a + b
+ end
+
+ it "can be made shareable for Ractor", :ractor do
+ add = FFI::Function.new(:int, [:int, :int], &method(:adder))
+ Ractor.make_shareable(add)
+
+ res = Ractor.new(add) do |add2|
+ LibTest.testFunctionAdd(10, 10, add2)
+ end.take
+
+ expect( res ).to eq(20)
+ end
+
+ it "should be usable with Ractor", :ractor do
+ res = Ractor.new do
+ function_add = FFI::Function.new(:int, [:int, :int]) { |a, b| a + b }
+ LibTest.testFunctionAdd(10, 10, function_add)
+ end.take
+
+ expect( res ).to eq(20)
+ end
+
it 'can be used to wrap an existing function pointer' do
expect(FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd')).call(10, 10)).to eq(20)
end
diff --git a/spec/ffi/function_type_spec.rb b/spec/ffi/function_type_spec.rb
index 54e1c48..2f171d8 100644
--- a/spec/ffi/function_type_spec.rb
+++ b/spec/ffi/function_type_spec.rb
@@ -8,7 +8,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
describe "FFI::FunctionType", skip: RUBY_ENGINE != "ruby" do
it 'is initialized with return type and a list of parameter types' do
function_type = FFI::FunctionType.new(:int, [ :char, :ulong ])
- expect(function_type.result_type).to be == FFI::Type::Builtin::INT
+ expect(function_type.return_type).to be == FFI::Type::Builtin::INT
expect(function_type.param_types).to be == [ FFI::Type::Builtin::CHAR, FFI::Type::Builtin::ULONG ]
end
diff --git a/spec/ffi/library_spec.rb b/spec/ffi/library_spec.rb
index a17d673..8691b17 100644
--- a/spec/ffi/library_spec.rb
+++ b/spec/ffi/library_spec.rb
@@ -5,6 +5,12 @@
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
+module TestEnumValueRactor
+ extend FFI::Library
+ enum :something, [:one, :two]
+ freeze
+end
+
describe "Library" do
describe ".enum_value" do
m = Module.new do
@@ -20,6 +26,14 @@ describe "Library" do
it "should return nil for an invalid key" do
expect(m.enum_value(:three)).to be nil
end
+
+ it "should be queryable in Ractor", :ractor do
+ res = Ractor.new do
+ TestEnumValueRactor.enum_value(:one)
+ end.take
+
+ expect( res ).to eq(0)
+ end
end
describe "#ffi_convention" do
@@ -184,15 +198,49 @@ describe "Library" do
end.getpid).to eq(Process.pid)
}.to raise_error(LoadError)
end
+ end
- it "attach_function :bool_return_true from [ File.expand_path(#{TestLibrary::PATH.inspect}) ]" do
- mod = Module.new do |m|
- m.extend FFI::Library
- ffi_lib File.expand_path(TestLibrary::PATH)
- attach_function :bool_return_true, [ ], :bool
- end
- expect(mod.bool_return_true).to be true
+ it "attach_function :bool_return_true from [ File.expand_path(#{TestLibrary::PATH.inspect}) ]" do
+ mod = Module.new do |m|
+ m.extend FFI::Library
+ ffi_lib File.expand_path(TestLibrary::PATH)
+ attach_function :bool_return_true, [ ], :bool
+ end
+ expect(mod.bool_return_true).to be true
+ end
+
+ it "can define a foo! function" do
+ mod = Module.new do |m|
+ m.extend FFI::Library
+ ffi_lib File.expand_path(TestLibrary::PATH)
+ attach_function :foo!, :bool_return_true, [], :bool
end
+ expect(mod.foo!).to be true
+ end
+
+ it "can define a foo? function" do
+ mod = Module.new do |m|
+ m.extend FFI::Library
+ ffi_lib File.expand_path(TestLibrary::PATH)
+ attach_function :foo?, :bool_return_true, [], :bool
+ end
+ expect(mod.foo?).to be true
+ end
+
+ it "can reveal the function type" do
+ skip 'this is not yet implemented on JRuby' if RUBY_ENGINE == 'jruby'
+ skip 'this is not yet implemented on Truffleruby' if RUBY_ENGINE == 'truffleruby'
+
+ mod = Module.new do |m|
+ m.extend FFI::Library
+ ffi_lib File.expand_path(TestLibrary::PATH)
+ attach_function :bool_return_true, [ :string ], :bool
+ end
+
+ fun = mod.attached_functions
+ expect(fun.keys).to eq([:bool_return_true])
+ expect(fun[:bool_return_true].param_types).to eq([FFI::Type::STRING])
+ expect(fun[:bool_return_true].return_type).to eq(FFI::Type::BOOL)
end
def gvar_lib(name, type)
@@ -323,20 +371,34 @@ describe "Library" do
end
end
- describe "Symbol" do
- before do
- @libtest = FFI::DynamicLibrary.open(
- TestLibrary::PATH,
- FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL,
- )
+ it "can reveal its attached global struct based variables" do
+ lib = Module.new do |m|
+ m.extend FFI::Library
+ ffi_lib TestLibrary::PATH
+ attach_variable :gvari, "gvar_gstruct", GlobalStruct
end
+ expect(lib.attached_variables).to eq({ gvari: GlobalStruct })
+ end
- it "has a memsize function", skip: RUBY_ENGINE != "ruby" do
- base_size = ObjectSpace.memsize_of(Object.new)
-
- symbol = @libtest.find_symbol("gvar_gstruct_set")
- size = ObjectSpace.memsize_of(symbol)
- expect(size).to be > base_size
+ it "can reveal its attached global variables" do
+ lib = Module.new do |m|
+ m.extend FFI::Library
+ ffi_lib TestLibrary::PATH
+ attach_variable "gvaro", "gvar_u32", :uint32
end
+ expect(lib.attached_variables).to eq({ gvaro: FFI::Type::UINT32 })
+ end
+
+ it "should have shareable constants for Ractor", :ractor do
+ res = Ractor.new do
+ [
+ FFI::Library::LIBC,
+ FFI::Library::CURRENT_PROCESS,
+ FFI::CURRENT_PROCESS,
+ FFI::USE_THIS_PROCESS_AS_LIBRARY,
+ ]
+ end.take
+
+ expect( res.size ).to be > 0
end
end
diff --git a/spec/ffi/memorypointer_spec.rb b/spec/ffi/memorypointer_spec.rb
index c8d1b42..f29e2cb 100644
--- a/spec/ffi/memorypointer_spec.rb
+++ b/spec/ffi/memorypointer_spec.rb
@@ -28,6 +28,21 @@ describe "MemoryPointer#total" do
expect(MemoryPointer.new(1024).total).to eq 1024
end
end
+describe "MemoryPointer#clear" do
+ it "should clear the memory" do
+ ptr = MemoryPointer.new(:long)
+ ptr.write_long 1234
+ expect(ptr.read_long).to eq(1234)
+ ptr.clear
+ expect(ptr.read_long).to eq(0)
+ end
+ it "should deny changes when frozen" do
+ skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
+ skip "not yet supported on JRuby" if RUBY_ENGINE == "jruby"
+ ptr = MemoryPointer.new(:long).freeze
+ expect{ ptr.clear }.to raise_error(RuntimeError, /memory write/)
+ end
+end
describe "MemoryPointer#read_array_of_long" do
it "foo" do
ptr = MemoryPointer.new(:long, 1024)
@@ -76,3 +91,22 @@ describe "MemoryPointer return value" do
expect(Stdio.fclose(fp)).to eq 0 unless fp.nil? or fp.null?
end
end
+describe "#autorelease" do
+ it "should be true by default" do
+ expect(MemoryPointer.new(8).autorelease?).to be true
+ end
+
+ it "should return false when autorelease=(false)" do
+ ptr = MemoryPointer.new(8)
+ ptr.autorelease = false
+ expect(ptr.autorelease?).to be false
+ ptr.free
+ end
+
+ it "should deny changes when frozen" do
+ skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
+ skip "not yet supported on JRuby" if RUBY_ENGINE == "jruby"
+ ptr = MemoryPointer.new(8).freeze
+ expect{ ptr.autorelease = false }.to raise_error(FrozenError)
+ end
+end
diff --git a/spec/ffi/platform_spec.rb b/spec/ffi/platform_spec.rb
index ad23621..8890b07 100644
--- a/spec/ffi/platform_spec.rb
+++ b/spec/ffi/platform_spec.rb
@@ -134,4 +134,17 @@ describe "FFI::Platform.unix?" do
expect(FFI::Platform::BYTE_ORDER).to eq(order)
end
end
+
+ it "should have shareable constants for Ractor", :ractor do
+ res = Ractor.new do
+ [
+ FFI::Platform::OS,
+ FFI::Platform::CPU,
+ FFI::Platform::ARCH,
+ FFI::Platform::OS,
+ ]
+ end.take
+
+ expect( res.size ).to be > 0
+ end
end
diff --git a/spec/ffi/pointer_spec.rb b/spec/ffi/pointer_spec.rb
index 820e144..8716d30 100644
--- a/spec/ffi/pointer_spec.rb
+++ b/spec/ffi/pointer_spec.rb
@@ -90,6 +90,13 @@ describe "Pointer" do
expect(PointerTestLib.ptr_ret_pointer(memory, 0).address).to eq(0xdeadbeef)
end
+ it "#write_pointer frozen object" do
+ skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby"
+ skip "not yet supported on JRuby" if RUBY_ENGINE == "jruby"
+ memory = FFI::MemoryPointer.new(:pointer).freeze
+ expect{ memory.write_pointer(PointerTestLib.ptr_from_address(0xdeadbeef)) }.to raise_error(RuntimeError, /memory write/)
+ end
+
it "#read_array_of_pointer" do
values = [0x12345678, 0xfeedf00d, 0xdeadbeef]
memory = FFI::MemoryPointer.new :pointer, values.size
@@ -364,6 +371,11 @@ describe "AutoPointer" do
ptr.autorelease = false
expect(ptr.autorelease?).to be false
end
+
+ it "should deny changes when frozen" do
+ ptr = ptr_class.new(FFI::Pointer.new(0xdeadbeef)).freeze
+ expect{ ptr.autorelease = false }.to raise_error(FrozenError)
+ end
end
describe "#type_size" do
diff --git a/spec/ffi/rbx/memory_pointer_spec.rb b/spec/ffi/rbx/memory_pointer_spec.rb
index 2db89a9..1270c9b 100644
--- a/spec/ffi/rbx/memory_pointer_spec.rb
+++ b/spec/ffi/rbx/memory_pointer_spec.rb
@@ -104,6 +104,18 @@ describe "MemoryPointer" do
expect(m.read FFI::Type::BOOL).to eq(false)
end
+ it "allows definition of a custom typedef" do
+ FFI.typedef :uint32, :fubar_t
+ expect(FFI.find_type(:fubar_t)).to eq(FFI::Type::Builtin::UINT32)
+ end
+
+ it "allows overwriting of a default typedef" do
+ FFI.typedef :uint32, :char
+ expect(FFI.find_type(:char)).to eq(FFI::Type::Builtin::UINT32)
+ ensure
+ FFI.typedef FFI::Type::Builtin::CHAR, :char
+ end
+
it "allows writing a custom typedef" do
FFI.typedef :uint, :fubar_t
FFI.typedef :size_t, :fubar2_t
diff --git a/spec/ffi/spec_helper.rb b/spec/ffi/spec_helper.rb
index a0adbed..22d1c47 100644
--- a/spec/ffi/spec_helper.rb
+++ b/spec/ffi/spec_helper.rb
@@ -9,6 +9,7 @@ require 'objspace'
RSpec.configure do |c|
c.filter_run_excluding gc_dependent: true unless ENV['FFI_TEST_GC'] == 'true'
+ c.filter_run_excluding( :ractor ) unless defined?(Ractor) && RUBY_VERSION >= "3.1"
end
module TestLibrary
diff --git a/spec/ffi/struct_by_ref_spec.rb b/spec/ffi/struct_by_ref_spec.rb
index 0858423..a775f92 100644
--- a/spec/ffi/struct_by_ref_spec.rb
+++ b/spec/ffi/struct_by_ref_spec.rb
@@ -39,5 +39,14 @@ describe FFI::Struct, ' by_ref' do
expect { @api.struct_test(other_class.new) }.to raise_error(TypeError)
end
+
+ it "can reveal the mapped type converter" do
+ skip 'this is not yet implemented on JRuby' if RUBY_ENGINE == 'jruby'
+ skip 'this is not yet implemented on Truffleruby' if RUBY_ENGINE == 'truffleruby'
+
+ param_type = @api.attached_functions[:struct_test].param_types[0]
+ expect(param_type).to be_a(FFI::Type::Mapped)
+ expect(param_type.converter).to be_a(FFI::StructByReference)
+ end
end
diff --git a/spec/ffi/struct_spec.rb b/spec/ffi/struct_spec.rb
index 0ce5273..ab62da1 100644
--- a/spec/ffi/struct_spec.rb
+++ b/spec/ffi/struct_spec.rb
@@ -410,6 +410,7 @@ module StructSpecsStructTests
s.pointer.put_double(0, 1.0)
expect(s.pointer.get_double(0)).to eq(1.0)
end
+
module EnumFields
extend FFI::Library
TestEnum = enum :test_enum, [:c1, 10, :c2, 20, :c3, 30, :c4, 40]
@@ -456,13 +457,24 @@ module StructSpecsStructTests
end
it "Can have CallbackInfo struct field" do
+ s = CallbackMember::TestStruct.new
+ add_proc = lambda { |a, b| a + b }
+ sub_proc = lambda { |a, b| a - b }
+ s[:add] = add_proc
+ s[:sub] = sub_proc
+ expect(CallbackMember.struct_call_add_cb(s, 40, 2)).to eq(42)
+ expect(CallbackMember.struct_call_sub_cb(s, 44, 2)).to eq(42)
+ end
+
+ it "Can use CallbackInfo struct field in Ractor", :ractor do
+ res = Ractor.new do
s = CallbackMember::TestStruct.new
- add_proc = lambda { |a, b| a+b }
- sub_proc = lambda { |a, b| a-b }
+ add_proc = lambda { |a, b| a + b }
s[:add] = add_proc
- s[:sub] = sub_proc
- expect(CallbackMember.struct_call_add_cb(s, 40, 2)).to eq(42)
- expect(CallbackMember.struct_call_sub_cb(s, 44, 2)).to eq(42)
+ CallbackMember.struct_call_add_cb(s, 40, 2)
+ end.take
+
+ expect( res ).to eq(42)
end
it "Can return its members as a list" do
@@ -532,6 +544,34 @@ module StructSpecsStructTests
expect(a.members).to eq([:a])
expect(b.members).to eq([:a, :b])
end
+
+ it "can be made shareable for Ractor", :ractor do
+ a = Class.new(FFI::Struct) do
+ layout :a, :char
+ end.new
+ a[:a] = -34
+ Ractor.make_shareable(a)
+
+ res = Ractor.new(a) do |a2|
+ a2[:a]
+ end.take
+
+ expect( res ).to eq(-34)
+ end
+
+ it "should be usable with Ractor", :ractor do
+ class TestStructRactor < FFI::Struct
+ layout :i, :int
+ end
+
+ res = Ractor.new do
+ s = TestStructRactor.new
+ s[:i] = 0x14
+ LibTest.ptr_ret_int32_t(s, 0)
+ end.take
+
+ expect( res ).to eq(0x14)
+ end
end
end
@@ -1069,6 +1109,20 @@ describe "Struct memsize functions", skip: RUBY_ENGINE != "ruby" do
size = ObjectSpace.memsize_of(layout[:pointer])
expect(size).to be > base_size
end
+
+ it "StructLayout should be shareable with Ractor", :ractor do
+ kl = Class.new(FFI::Struct) do
+ layout :ptr, :pointer
+ end
+ expect(Ractor.shareable?(kl.layout)).to eq(true)
+ end
+
+ it "StructField should be shareable with Ractor", :ractor do
+ kl = Class.new(FFI::Struct) do
+ layout :ptr, :pointer
+ end
+ expect(Ractor.shareable?(kl.layout[:ptr])).to eq(true)
+ end
end
diff --git a/spec/ffi/type_spec.rb b/spec/ffi/type_spec.rb
index eb48a43..e880c83 100644
--- a/spec/ffi/type_spec.rb
+++ b/spec/ffi/type_spec.rb
@@ -5,7 +5,7 @@
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
-describe "FFI::Type" do
+describe FFI::Type do
it 'has a memsize function', skip: RUBY_ENGINE != "ruby" do
base_size = ObjectSpace.memsize_of(Object.new)
@@ -40,4 +40,37 @@ describe "FFI::Type" do
size = ObjectSpace.memsize_of(FFI::Type::Builtin::CHAR)
expect(size).to be > base_size
end
+
+ it "should be shareable with Ractor", :ractor do
+ expect(Ractor.shareable?(FFI::Type.new(5))).to eq(true)
+ end
+
+ describe :Builtin do
+ it "should be shareable with Ractor", :ractor do
+ expect(Ractor.shareable?(FFI::Type::INT32)).to eq(true)
+ end
+ end
+
+ describe :Mapped do
+ it "should be shareable with Ractor", :ractor do
+ converter = Module.new do
+ extend FFI::DataConverter
+
+ def self.native_type
+ FFI::Type::INT32
+ end
+
+ def self.to_native(val, ctx)
+ ToNativeMap[val]
+ end
+
+ def self.from_native(val, ctx)
+ FromNativeMap[val]
+ end
+ end
+ expect(Ractor.shareable?(converter)).to eq(true)
+ type = FFI::Type::Mapped.new(converter)
+ expect(Ractor.shareable?(type)).to eq(true)
+ end
+ end
end
diff --git a/spec/ffi/variadic_spec.rb b/spec/ffi/variadic_spec.rb
index f379ed4..b528c09 100644
--- a/spec/ffi/variadic_spec.rb
+++ b/spec/ffi/variadic_spec.rb
@@ -23,6 +23,8 @@ describe "Function with variadic arguments" do
attach_function :testBlockingClose, [ :pointer ], :void
attach_function :testCallbackVrDva, :testClosureVrDva, [ :double, :varargs ], :double
attach_function :testCallbackVrILva, :testClosureVrILva, [ :int, :long, :varargs ], :long
+
+ freeze
end
it "takes enum arguments" do
@@ -37,6 +39,15 @@ describe "Function with variadic arguments" do
expect(LibTest.pack_varargs2(buf, :c1, "ii", :int, :c3, :int, :c4)).to eq(:c2)
end
+ it "can reveal its return and parameters" do
+ skip 'this is not yet implemented on JRuby' if RUBY_ENGINE == 'jruby'
+ skip 'this is not yet implemented on Truffleruby' if RUBY_ENGINE == 'truffleruby'
+
+ fun = LibTest.attached_functions[:testBlockingWRva]
+ expect(fun.param_types).to eq([FFI::Type::POINTER, FFI::Type::CHAR, FFI::Type::VARARGS])
+ expect(fun.return_type).to eq(FFI::Type::INT8)
+ end
+
it 'can wrap a blocking function with varargs' do
handle = LibTest.testBlockingOpen
expect(handle).not_to be_null
@@ -87,12 +98,33 @@ describe "Function with variadic arguments" do
expect(LibTest.testCallbackVrDva(3.0, :cbVrD, pr)).to be_within(0.0000001).of(45.0)
end
+ it "can be called as instance method" do
+ kl = Class.new do
+ include LibTest
+ def call
+ pr = proc { 42.0 }
+ testCallbackVrDva(3.0, :cbVrD, pr)
+ end
+ end
+ expect(kl.new.call).to be_within(0.0000001).of(45.0)
+ end
+
it "call variadic with several callback arguments" do
pr1 = proc { |i| i + 1 }
pr2 = proc { |l| l + 2 }
expect(LibTest.testCallbackVrILva(5, 6, :cbVrI, pr1, :cbVrL, pr2)).to eq(14)
end
+ it "should be usable with Ractor", :ractor do
+ res = Ractor.new do
+ pr = proc { 42.0 }
+ LibTest.testCallbackVrDva(3.0, :cbVrD, pr)
+ end.take
+
+ expect(res).to be_within(0.0000001).of(45.0)
+ end
+
+
module Varargs
PACK_VALUES = {
'c' => [ 0x12 ],