From fb4106ff1d056b66ca045f71bdf6587418309527 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 23 Jun 2026 13:16:51 +0200 Subject: [PATCH] Ractor shareable results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requires a new Ruby feature: https://bugs.ruby-lang.org/issues/22128 ``` ruby 4.1.0dev (2026-06-24T13:17:28Z expose-ractor-set-.. f9d7dd50cd) +PRISM [arm64-darwin25] Warming up -------------------------------------- baseline 42.000 i/100ms shareable 66.000 i/100ms Calculating ------------------------------------- baseline 220.024 (±43.2%) i/s (4.54 ms/i) - 1.134k in 5.153976s shareable 677.487 (± 2.4%) i/s (1.48 ms/i) - 3.432k in 5.065782s Comparison: baseline: 220.0 i/s shareable: 677.5 i/s - 3.08x faster ``` --- contrib/ruby/Gemfile.lock | 33 -------------------- contrib/ruby/ext/trilogy-ruby/cast.c | 15 ++++++--- contrib/ruby/ext/trilogy-ruby/cext.c | 19 +++++++++-- contrib/ruby/ext/trilogy-ruby/extconf.rb | 1 + contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h | 16 ++++++++++ contrib/ruby/script/benchmark | 10 ++++++ contrib/ruby/test/client_test.rb | 6 ++++ 7 files changed, 59 insertions(+), 41 deletions(-) delete mode 100644 contrib/ruby/Gemfile.lock diff --git a/contrib/ruby/Gemfile.lock b/contrib/ruby/Gemfile.lock deleted file mode 100644 index de772609..00000000 --- a/contrib/ruby/Gemfile.lock +++ /dev/null @@ -1,33 +0,0 @@ -PATH - remote: . - specs: - trilogy (2.12.6) - bigdecimal - -GEM - remote: https://rubygems.org/ - specs: - benchmark-ips (2.14.0) - bigdecimal (4.0.1) - minitest (5.26.1) - mutex_m (0.3.0) - mysql2 (0.5.7) - bigdecimal - rake (13.3.1) - rake-compiler (1.3.1) - rake - -PLATFORMS - arm64-darwin-23 - ruby - -DEPENDENCIES - benchmark-ips - minitest (~> 5.5, <= 5.26.1) - mutex_m - mysql2 - rake-compiler - trilogy! - -BUNDLED WITH - 2.5.23 diff --git a/contrib/ruby/ext/trilogy-ruby/cast.c b/contrib/ruby/ext/trilogy-ruby/cast.c index 1377c7dc..a463e476 100644 --- a/contrib/ruby/ext/trilogy-ruby/cast.c +++ b/contrib/ruby/ext/trilogy-ruby/cast.c @@ -234,7 +234,8 @@ rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *co if (column->decimals == 0 && !options->cast_decimals_to_bigdecimals) { return rb_funcall(rb_mKernel, id_Integer, 1, str); } else { - return rb_funcall(rb_mKernel, id_BigDecimal, 1, str); + VALUE bigdecimal = rb_funcall(rb_mKernel, id_BigDecimal, 1, str); + return rb_trilogy_shareable(bigdecimal, options); } } case TRILOGY_TYPE_FLOAT: @@ -311,8 +312,9 @@ rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *co rb_raise(Trilogy_CastError, "Invalid date: %.*s", (int)value->data_len, (char *)value->data); } - return trilogy_make_time(year, month, day, hour, min, sec, usec, + VALUE time = trilogy_make_time(year, month, day, hour, min, sec, usec, options->database_local_time); + return rb_trilogy_shareable(time, options); } case TRILOGY_TYPE_DATE: { const char *p = (const char *)value->data; @@ -343,7 +345,8 @@ rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *co rb_raise(Trilogy_CastError, "Invalid date: %.*s", (int)value->data_len, (char *)value->data); } - return rb_funcall(Date, id_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day)); + VALUE date = rb_funcall(Date, id_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day)); + return rb_trilogy_shareable(date, options); } case TRILOGY_TYPE_TIME: { const char *p = (const char *)value->data; @@ -375,8 +378,9 @@ rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *co return Qnil; } - return trilogy_make_time(2000, 1, 1, hour, min, sec, usec, + VALUE time = trilogy_make_time(2000, 1, 1, hour, min, sec, usec, options->database_local_time); + return rb_trilogy_shareable(time, options); } default: break; @@ -384,7 +388,8 @@ rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *co } // for all other types, just return a string - return rb_enc_str_new(value->data, value->data_len, encoding_for_charset(column->charset)); + VALUE string = rb_enc_str_new(value->data, value->data_len, encoding_for_charset(column->charset)); + return rb_trilogy_shareable(string, options); } void rb_trilogy_cast_init(void) diff --git a/contrib/ruby/ext/trilogy-ruby/cext.c b/contrib/ruby/ext/trilogy-ruby/cext.c index f8209a48..89deb325 100644 --- a/contrib/ruby/ext/trilogy-ruby/cext.c +++ b/contrib/ruby/ext/trilogy-ruby/cext.c @@ -164,7 +164,7 @@ static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows, id_password, id_database, id_enable_cleartext_plugin, id_ssl_ca, id_ssl_capath, id_ssl_cert, id_ssl_cipher, id_ssl_crl, id_ssl_crlpath, id_ssl_key, id_ssl_mode, id_tls_ciphersuites, id_tls_min_version, id_tls_max_version, id_multi_statement, id_multi_result, - id_from_code, id_from_errno, id_max_allowed_packet; + id_from_code, id_from_errno, id_max_allowed_packet, id_shareable; struct trilogy_ctx { trilogy_conn_t conn; @@ -764,6 +764,10 @@ static VALUE rb_trilogy_connect(VALUE self, VALUE raw_socket, VALUE encoding, VA connopt.tls_max_version = NUM2INT(val); } + if (RTEST(rb_hash_aref(opts, ID2SYM(id_shareable)))) { + ctx->query_flags |= TRILOGY_FLAGS_CAST_SHAREABLE; + } + VALUE io = rb_io_get_io(raw_socket); rb_io_t *fptr; @@ -880,6 +884,7 @@ static void load_query_options(unsigned int query_flags, struct rb_trilogy_cast_ cast_options->cast_decimals_to_bigdecimals = (query_flags & TRILOGY_FLAGS_CAST_ALL_DECIMALS_TO_BIGDECIMALS) != 0; cast_options->database_local_time = (query_flags & TRILOGY_FLAGS_LOCAL_TIMEZONE) != 0; cast_options->flatten_rows = (query_flags & TRILOGY_FLAGS_FLATTEN_ROWS) != 0; + cast_options->shareable = (query_flags & TRILOGY_FLAGS_CAST_SHAREABLE) != 0; } struct read_query_response_state { @@ -990,6 +995,7 @@ static VALUE read_query_response(VALUE vargs) row_ruby_values[i] = rb_enc_str_new(column.name, column.name_len, ctx->encoding); OBJ_FREEZE(row_ruby_values[i]); #endif + rb_trilogy_shareable(row_ruby_values[i], args->cast_options); column_info[i].type = column.type; column_info[i].flags = column.flags; @@ -999,6 +1005,7 @@ static VALUE read_query_response(VALUE vargs) } column_names = rb_ary_new_from_values(column_count, row_ruby_values); + rb_trilogy_shareable(column_names, args->cast_options); VALUE rb_trilogy_values; trilogy_value_t *row_trilogy_values = ALLOCV_N(trilogy_value_t, rb_trilogy_values, column_count); @@ -1029,16 +1036,19 @@ static VALUE read_query_response(VALUE vargs) if (args->cast_options->flatten_rows) { rb_ary_cat(rows, row_ruby_values, column_count); } else { - rb_ary_push(rows, rb_ary_new_from_values(column_count, row_ruby_values)); + VALUE row = rb_ary_new_from_values(column_count, row_ruby_values); + rb_ary_push(rows, rb_trilogy_shareable(row, args->cast_options)); } } + rb_trilogy_shareable(rows, args->cast_options); + ALLOCV_END(rb_column_info); ALLOCV_END(rb_trilogy_values); ALLOCV_END(rb_ruby_values); } - return rb_class_new_instance( + VALUE result = rb_class_new_instance( 6, (VALUE []){ column_names, @@ -1050,6 +1060,7 @@ static VALUE read_query_response(VALUE vargs) }, Trilogy_Result ); + return rb_trilogy_shareable(result, args->cast_options); } static VALUE execute_read_query_response(struct trilogy_ctx *ctx) @@ -1415,6 +1426,7 @@ RUBY_FUNC_EXPORTED void Init_cext(void) rb_define_const(Trilogy, "QUERY_FLAGS_CAST", INT2NUM(TRILOGY_FLAGS_CAST)); rb_define_const(Trilogy, "QUERY_FLAGS_CAST_BOOLEANS", INT2NUM(TRILOGY_FLAGS_CAST_BOOLEANS)); rb_define_const(Trilogy, "QUERY_FLAGS_CAST_ALL_DECIMALS_TO_BIGDECIMALS", INT2NUM(TRILOGY_FLAGS_CAST_ALL_DECIMALS_TO_BIGDECIMALS)); + rb_define_const(Trilogy, "QUERY_FLAGS_CAST_SHAREABLE", INT2NUM(TRILOGY_FLAGS_CAST_SHAREABLE)); rb_define_const(Trilogy, "QUERY_FLAGS_LOCAL_TIMEZONE", INT2NUM(TRILOGY_FLAGS_LOCAL_TIMEZONE)); rb_define_const(Trilogy, "QUERY_FLAGS_FLATTEN_ROWS", INT2NUM(TRILOGY_FLAGS_FLATTEN_ROWS)); rb_define_const(Trilogy, "QUERY_FLAGS_DEFAULT", INT2NUM(TRILOGY_FLAGS_DEFAULT)); @@ -1483,6 +1495,7 @@ RUBY_FUNC_EXPORTED void Init_cext(void) id_multi_result = rb_intern("multi_result"); id_from_code = rb_intern("from_code"); id_from_errno = rb_intern("from_errno"); + id_shareable = rb_intern("shareable"); rb_trilogy_cast_init(); diff --git a/contrib/ruby/ext/trilogy-ruby/extconf.rb b/contrib/ruby/ext/trilogy-ruby/extconf.rb index c5fc8e44..fe0dafb6 100644 --- a/contrib/ruby/ext/trilogy-ruby/extconf.rb +++ b/contrib/ruby/ext/trilogy-ruby/extconf.rb @@ -29,5 +29,6 @@ have_func("rb_ractor_local_storage_value_newkey", "ruby.h") have_func("rb_enc_interned_str", "ruby.h") have_func("rb_io_descriptor", "ruby.h") # Ruby 3.1+ +# have_func("rb_obj_set_shareable", "ruby/ractor.h") create_makefile "trilogy/cext" diff --git a/contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h b/contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h index 4afd548e..f5412533 100644 --- a/contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h +++ b/contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h @@ -2,6 +2,7 @@ #define TRILOGY_RUBY_H #include +#include #include #include @@ -12,6 +13,7 @@ #define TRILOGY_FLAGS_LOCAL_TIMEZONE 4 #define TRILOGY_FLAGS_FLATTEN_ROWS 8 #define TRILOGY_FLAGS_CAST_ALL_DECIMALS_TO_BIGDECIMALS 16 +#define TRILOGY_FLAGS_CAST_SHAREABLE 32 #define TRILOGY_FLAGS_DEFAULT (TRILOGY_FLAGS_CAST) struct rb_trilogy_cast_options { @@ -20,6 +22,7 @@ struct rb_trilogy_cast_options { bool database_local_time; bool flatten_rows; bool cast_decimals_to_bigdecimals; + bool shareable; }; struct column_info { @@ -32,6 +35,19 @@ struct column_info { extern VALUE Trilogy_CastError; +static inline VALUE rb_trilogy_shareable(VALUE obj, const struct rb_trilogy_cast_options *cast_options) +{ + if (cast_options->shareable) { + RB_OBJ_SET_FROZEN_SHAREABLE(obj); + // OBJ_FREEZE(obj); +// #ifdef RB_OBJ_SET_SHAREABLE +// VALUE rb_obj_set_shareable(VALUE); +// rb_obj_set_shareable(obj); +// #endif + } + return obj; +} + VALUE rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *column, const struct rb_trilogy_cast_options *options); diff --git a/contrib/ruby/script/benchmark b/contrib/ruby/script/benchmark index 1e24830a..3691a21c 100755 --- a/contrib/ruby/script/benchmark +++ b/contrib/ruby/script/benchmark @@ -120,6 +120,16 @@ Benchmark.ips do |x| x.compare! end +baseline = Trilogy.new(connect_options) +shareable = Trilogy.new(connect_options.merge(shareable: true)) + +Benchmark.ips do |x| + x.report("baseline") { Ractor.make_shareable(baseline.query("SELECT * FROM trilogy_test")) } + x.report("shareable") { Ractor.make_shareable(shareable.query("SELECT * FROM trilogy_test")) } + x.compare!(order: :baseline) +end + + QUERY = "SELECT 1" Benchmark.ips do |x| diff --git a/contrib/ruby/test/client_test.rb b/contrib/ruby/test/client_test.rb index 91b799c1..74c6d0e6 100644 --- a/contrib/ruby/test/client_test.rb +++ b/contrib/ruby/test/client_test.rb @@ -1268,6 +1268,12 @@ def test_is_ractor_compatible end assert_equal [[1]], ractor.value.to_a end + + def test_shareable_result + client = new_tcp_client(shareable: true) + result = client.query("SELECT * FROM test.trilogy_test") + assert_predicate result, :frozen? + end end if defined?(Process.fork)