summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/actions/setup/directories/action.yml2
-rw-r--r--bootstraptest/test_yjit.rb2
-rw-r--r--ext/socket/lib/socket.rb34
-rw-r--r--gc.c10
-rw-r--r--hash.c9
-rw-r--r--include/ruby/internal/fl_type.h35
-rw-r--r--internal/thread.h2
-rw-r--r--misc/lldb_rb/commands/print_flags_command.py2
-rw-r--r--object.c4
-rw-r--r--ractor.c40
-rw-r--r--rubyparser.h2
-rw-r--r--scheduler.c29
-rw-r--r--shape.c1
-rw-r--r--shape.h31
-rw-r--r--st.c9
-rw-r--r--string.c2
-rw-r--r--test/ruby/test_object_id.rb31
-rw-r--r--test/ruby/test_variable.rb13
-rw-r--r--test/socket/test_socket.rb26
-rw-r--r--thread.c31
-rw-r--r--variable.c121
-rw-r--r--vm_insnhelper.c2
-rw-r--r--yjit/src/cruby_bindings.inc.rs3
-rw-r--r--zjit/src/cruby_bindings.inc.rs3
24 files changed, 265 insertions, 179 deletions
diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml
index 48e2c64a96..f16ce21e0e 100644
--- a/.github/actions/setup/directories/action.yml
+++ b/.github/actions/setup/directories/action.yml
@@ -183,3 +183,5 @@ runs:
${{ steps.clean.outputs.distclean }}
${{ steps.clean.outputs.remained-files }}
${{ steps.clean.outputs.final }}
+ # rmdir randomly fails due to launchable files
+ continue-on-error: true
diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb
index 8d02998254..d480369c75 100644
--- a/bootstraptest/test_yjit.rb
+++ b/bootstraptest/test_yjit.rb
@@ -220,7 +220,7 @@ assert_equal 'Sub', %q{
call(Sub.new('o')).class
}
-# String#dup with FL_EXIVAR
+# String#dup with generic ivars
assert_equal '["str", "ivar"]', %q{
def str_dup(str) = str.dup
str = "str"
diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb
index 60dd45bd4f..7c3f6f5b91 100644
--- a/ext/socket/lib/socket.rb
+++ b/ext/socket/lib/socket.rb
@@ -643,6 +643,7 @@ class Socket < BasicSocket
#
# [:resolv_timeout] Specifies the timeout in seconds from when the hostname resolution starts.
# [:connect_timeout] This method sequentially attempts connecting to all candidate destination addresses.<br>The +connect_timeout+ specifies the timeout in seconds from the start of the connection attempt to the last candidate.<br>By default, all connection attempts continue until the timeout occurs.<br>When +fast_fallback:false+ is explicitly specified,<br>a timeout is set for each connection attempt and any connection attempt that exceeds its timeout will be canceled.
+ # [:open_timeout] Specifies the timeout in seconds from the start of the method execution.<br>If this timeout is reached while there are still addresses that have not yet been attempted for connection, no further attempts will be made.
# [:fast_fallback] Enables the Happy Eyeballs Version 2 algorithm (enabled by default).
#
# If a block is given, the block is called with the socket.
@@ -656,11 +657,16 @@ class Socket < BasicSocket
# sock.close_write
# puts sock.read
# }
- def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket
+ def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket
+
+ if open_timeout && (connect_timeout || resolv_timeout)
+ raise ArgumentError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"
+ end
+
sock = if fast_fallback && !(host && ip_address?(host))
- tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:)
+ tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:)
else
- tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:)
+ tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:)
end
if block_given?
@@ -674,7 +680,7 @@ class Socket < BasicSocket
end
end
- def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil)
+ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil)
if local_host || local_port
local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, timeout: resolv_timeout)
resolving_family_names = local_addrinfos.map { |lai| ADDRESS_FAMILIES.key(lai.afamily) }.uniq
@@ -692,6 +698,7 @@ class Socket < BasicSocket
resolution_delay_expires_at = nil
connection_attempt_delay_expires_at = nil
user_specified_connect_timeout_at = nil
+ user_specified_open_timeout_at = open_timeout ? now + open_timeout : nil
last_error = nil
last_error_from_thread = false
@@ -784,7 +791,10 @@ class Socket < BasicSocket
ends_at =
if resolution_store.any_addrinfos?
- resolution_delay_expires_at || connection_attempt_delay_expires_at
+ [(resolution_delay_expires_at || connection_attempt_delay_expires_at),
+ user_specified_open_timeout_at].compact.min
+ elsif user_specified_open_timeout_at
+ user_specified_open_timeout_at
else
[user_specified_resolv_timeout_at, user_specified_connect_timeout_at].compact.max
end
@@ -885,6 +895,8 @@ class Socket < BasicSocket
end
end
+ raise(Errno::ETIMEDOUT, 'user specified timeout') if expired?(now, user_specified_open_timeout_at)
+
if resolution_store.empty_addrinfos?
if connecting_sockets.empty? && resolution_store.resolved_all_families?
if last_error_from_thread
@@ -912,7 +924,7 @@ class Socket < BasicSocket
end
end
- def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:)
+ def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:)
last_error = nil
ret = nil
@@ -921,7 +933,10 @@ class Socket < BasicSocket
local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil)
end
- Addrinfo.foreach(host, port, nil, :STREAM, timeout: resolv_timeout) {|ai|
+ timeout = open_timeout ? open_timeout : resolv_timeout
+ starts_at = current_clock_time
+
+ Addrinfo.foreach(host, port, nil, :STREAM, timeout:) {|ai|
if local_addr_list
local_addr = local_addr_list.find {|local_ai| local_ai.afamily == ai.afamily }
next unless local_addr
@@ -929,9 +944,10 @@ class Socket < BasicSocket
local_addr = nil
end
begin
+ timeout = open_timeout ? open_timeout - (current_clock_time - starts_at) : connect_timeout
sock = local_addr ?
- ai.connect_from(local_addr, timeout: connect_timeout) :
- ai.connect(timeout: connect_timeout)
+ ai.connect_from(local_addr, timeout:) :
+ ai.connect(timeout:)
rescue SystemCallError
last_error = $!
next
diff --git a/gc.c b/gc.c
index aefe8a116b..eacd8dae86 100644
--- a/gc.c
+++ b/gc.c
@@ -2070,9 +2070,8 @@ rb_gc_obj_free_vm_weak_references(VALUE obj)
{
obj_free_object_id(obj);
- if (FL_TEST_RAW(obj, FL_EXIVAR)) {
+ if (rb_obj_exivar_p(obj)) {
rb_free_generic_ivar((VALUE)obj);
- FL_UNSET_RAW(obj, FL_EXIVAR);
}
switch (BUILTIN_TYPE(obj)) {
@@ -2317,7 +2316,7 @@ rb_obj_memsize_of(VALUE obj)
return 0;
}
- if (FL_TEST(obj, FL_EXIVAR)) {
+ if (rb_obj_exivar_p(obj)) {
size += rb_generic_ivar_memsize(obj);
}
@@ -3142,7 +3141,7 @@ rb_gc_mark_children(void *objspace, VALUE obj)
{
struct gc_mark_classext_foreach_arg foreach_args;
- if (FL_TEST_RAW(obj, FL_EXIVAR)) {
+ if (rb_obj_exivar_p(obj)) {
rb_mark_generic_ivar(obj);
}
@@ -4011,8 +4010,7 @@ vm_weak_table_gen_fields_foreach(st_data_t key, st_data_t value, st_data_t data)
case ST_DELETE:
free_gen_fields_tbl((VALUE)key, (struct gen_fields_tbl *)value);
-
- FL_UNSET((VALUE)key, FL_EXIVAR);
+ RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID);
return ST_DELETE;
case ST_REPLACE: {
diff --git a/hash.c b/hash.c
index 2cc6828bb0..be26e0eb3f 100644
--- a/hash.c
+++ b/hash.c
@@ -1597,10 +1597,11 @@ VALUE
rb_hash_dup(VALUE hash)
{
const VALUE flags = RBASIC(hash)->flags;
- VALUE ret = hash_dup(hash, rb_obj_class(hash),
- flags & (FL_EXIVAR|RHASH_PROC_DEFAULT));
- if (flags & FL_EXIVAR)
+ VALUE ret = hash_dup(hash, rb_obj_class(hash), flags & RHASH_PROC_DEFAULT);
+
+ if (rb_obj_exivar_p(hash)) {
rb_copy_generic_ivar(ret, hash);
+ }
return ret;
}
@@ -2920,7 +2921,7 @@ hash_aset(st_data_t *key, st_data_t *val, struct update_arg *arg, int existing)
VALUE
rb_hash_key_str(VALUE key)
{
- if (!RB_FL_ANY_RAW(key, FL_EXIVAR) && RBASIC_CLASS(key) == rb_cString) {
+ if (!rb_obj_exivar_p(key) && RBASIC_CLASS(key) == rb_cString) {
return rb_fstring(key);
}
else {
diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h
index 701118ef25..e52ccecedd 100644
--- a/include/ruby/internal/fl_type.h
+++ b/include/ruby/internal/fl_type.h
@@ -253,6 +253,21 @@ ruby_fl_type {
= 0,
/**
+ * @deprecated This flag was an implementation detail that should never have
+ * no been exposed. Exists here for backwards
+ * compatibility only. You can safely forget about it.
+ */
+ RUBY_FL_EXIVAR
+
+#if defined(RBIMPL_HAVE_ENUM_ATTRIBUTE)
+ RBIMPL_ATTR_DEPRECATED(("FL_EXIVAR is an outdated implementation detail, it shoudl be used."))
+#elif defined(_MSC_VER)
+# pragma deprecated(RUBY_FL_EXIVAR)
+#endif
+
+ = 0,
+
+ /**
* This flag has something to do with Ractor. Multiple Ractors run without
* protecting each other. Sharing an object among Ractors are basically
* dangerous, disabled by default. This flag is used to bypass that
@@ -286,18 +301,12 @@ ruby_fl_type {
*/
RUBY_FL_UNUSED9 = (1<<9),
- /**
- * This flag has something to do with instance variables. 3rd parties need
- * not know, but there are several ways to store an object's instance
- * variables. Objects with this flag use so-called "generic" backend
- * storage. This distinction is purely an implementation detail. People
- * need not be aware of this working behind-the-scene.
- *
- * @internal
- *
- * As of writing everything except ::RObject and RModule use this scheme.
- */
- RUBY_FL_EXIVAR = (1<<10),
+ /**
+ * This flag is no longer in use
+ *
+ * @internal
+ */
+ RUBY_FL_UNUSED10 = (1<<10),
/**
* This flag has something to do with data immutability. When this flag is
@@ -399,7 +408,7 @@ enum {
# pragma deprecated(RUBY_FL_DUPPED)
#endif
- = (int)RUBY_T_MASK | (int)RUBY_FL_EXIVAR
+ = (int)RUBY_T_MASK
};
#undef RBIMPL_HAVE_ENUM_ATTRIBUTE
diff --git a/internal/thread.h b/internal/thread.h
index 00fcbfc560..928126c3b0 100644
--- a/internal/thread.h
+++ b/internal/thread.h
@@ -83,6 +83,8 @@ RUBY_SYMBOL_EXPORT_END
int rb_threadptr_execute_interrupts(struct rb_thread_struct *th, int blocking_timing);
bool rb_thread_mn_schedulable(VALUE thread);
+bool rb_thread_resolve_unblock_function(rb_unblock_function_t **unblock_function, void **data2, struct rb_thread_struct *thread);
+
// interrupt exec
typedef VALUE (rb_interrupt_exec_func_t)(void *data);
diff --git a/misc/lldb_rb/commands/print_flags_command.py b/misc/lldb_rb/commands/print_flags_command.py
index 2b056dd098..bc494ae01a 100644
--- a/misc/lldb_rb/commands/print_flags_command.py
+++ b/misc/lldb_rb/commands/print_flags_command.py
@@ -17,7 +17,7 @@ class PrintFlagsCommand(RbBaseCommand):
flags = [
"RUBY_FL_WB_PROTECTED", "RUBY_FL_PROMOTED", "RUBY_FL_FINALIZE",
- "RUBY_FL_SHAREABLE", "RUBY_FL_EXIVAR", "RUBY_FL_FREEZE",
+ "RUBY_FL_SHAREABLE", "RUBY_FL_FREEZE",
"RUBY_FL_USER0", "RUBY_FL_USER1", "RUBY_FL_USER2", "RUBY_FL_USER3", "RUBY_FL_USER4",
"RUBY_FL_USER5", "RUBY_FL_USER6", "RUBY_FL_USER7", "RUBY_FL_USER8", "RUBY_FL_USER9",
"RUBY_FL_USER10", "RUBY_FL_USER11", "RUBY_FL_USER12", "RUBY_FL_USER13", "RUBY_FL_USER14",
diff --git a/object.c b/object.c
index a4da42d12f..03474389fd 100644
--- a/object.c
+++ b/object.c
@@ -373,9 +373,9 @@ init_copy(VALUE dest, VALUE obj)
if (OBJ_FROZEN(dest)) {
rb_raise(rb_eTypeError, "[bug] frozen object (%s) allocated", rb_obj_classname(dest));
}
- RBASIC(dest)->flags &= ~(T_MASK|FL_EXIVAR);
+ RBASIC(dest)->flags &= ~T_MASK;
// Copies the shape id from obj to dest
- RBASIC(dest)->flags |= RBASIC(obj)->flags & (T_MASK|FL_EXIVAR);
+ RBASIC(dest)->flags |= RBASIC(obj)->flags & T_MASK;
switch (BUILTIN_TYPE(obj)) {
case T_IMEMO:
rb_bug("Unreacheable");
diff --git a/ractor.c b/ractor.c
index c812277296..cce376c543 100644
--- a/ractor.c
+++ b/ractor.c
@@ -1358,24 +1358,24 @@ make_shareable_check_shareable(VALUE obj)
}
switch (TYPE(obj)) {
- case T_IMEMO:
- return traverse_skip;
- case T_OBJECT:
- {
- // If a T_OBJECT is shared and has no free capacity, we can't safely store the object_id inline,
- // as it would require to move the object content into an external buffer.
- // This is only a problem for T_OBJECT, given other types have external fields and can do RCU.
- // To avoid this issue, we proactively create the object_id.
- shape_id_t shape_id = RBASIC_SHAPE_ID(obj);
- attr_index_t capacity = RSHAPE_CAPACITY(shape_id);
- attr_index_t free_capacity = capacity - RSHAPE_LEN(shape_id);
- if (!rb_shape_has_object_id(shape_id) && capacity && !free_capacity) {
- rb_obj_id(obj);
- }
- }
- break;
- default:
- break;
+ case T_IMEMO:
+ return traverse_skip;
+ case T_OBJECT:
+ {
+ // If a T_OBJECT is shared and has no free capacity, we can't safely store the object_id inline,
+ // as it would require to move the object content into an external buffer.
+ // This is only a problem for T_OBJECT, given other types have external fields and can do RCU.
+ // To avoid this issue, we proactively create the object_id.
+ shape_id_t shape_id = RBASIC_SHAPE_ID(obj);
+ attr_index_t capacity = RSHAPE_CAPACITY(shape_id);
+ attr_index_t free_capacity = capacity - RSHAPE_LEN(shape_id);
+ if (!rb_shape_has_object_id(shape_id) && capacity && !free_capacity) {
+ rb_obj_id(obj);
+ }
+ }
+ break;
+ default:
+ break;
}
if (!RB_OBJ_FROZEN_RAW(obj)) {
@@ -1656,7 +1656,7 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data)
else if (data->replacement != _val) { RB_OBJ_WRITE(obj, &v, data->replacement); } \
} while (0)
- if (UNLIKELY(FL_TEST_RAW(obj, FL_EXIVAR))) {
+ if (UNLIKELY(rb_obj_exivar_p(obj))) {
struct gen_fields_tbl *fields_tbl;
rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl);
@@ -1885,7 +1885,7 @@ move_leave(VALUE obj, struct obj_traverse_replace_data *data)
rb_gc_obj_id_moved(data->replacement);
- if (UNLIKELY(FL_TEST_RAW(obj, FL_EXIVAR))) {
+ if (UNLIKELY(rb_obj_exivar_p(obj))) {
rb_replace_generic_ivar(data->replacement, obj);
}
diff --git a/rubyparser.h b/rubyparser.h
index 16f5cac81f..7525069fcb 100644
--- a/rubyparser.h
+++ b/rubyparser.h
@@ -1153,7 +1153,7 @@ typedef struct RNode_ERROR {
#define RNODE_FILE(node) ((rb_node_file_t *)(node))
#define RNODE_ENCODING(node) ((rb_node_encoding_t *)(node))
-/* FL : 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: FINALIZE, 8: UNUSED, 9: UNUSED, 10: EXIVAR, 11: FREEZE */
+/* FL : 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: FINALIZE, 8..10: UNUSED, 11: FREEZE */
/* NODE_FL: 0..4: UNUSED, 5: UNUSED, 6: UNUSED, 7: NODE_FL_NEWLINE,
* 8..14: nd_type,
* 15..: nd_line
diff --git a/scheduler.c b/scheduler.c
index 11faca01d3..83b9681cc3 100644
--- a/scheduler.c
+++ b/scheduler.c
@@ -63,8 +63,10 @@ typedef enum {
struct rb_fiber_scheduler_blocking_operation {
void *(*function)(void *);
void *data;
+
rb_unblock_function_t *unblock_function;
void *data2;
+
int flags;
struct rb_fiber_scheduler_blocking_operation_state *state;
@@ -208,7 +210,10 @@ rb_fiber_scheduler_blocking_operation_execute(rb_fiber_scheduler_blocking_operat
return -1; // Invalid blocking operation
}
- // Atomically check if we can transition from QUEUED to EXECUTING
+ // Resolve sentinel values for unblock_function and data2:
+ rb_thread_resolve_unblock_function(&blocking_operation->unblock_function, &blocking_operation->data2, GET_THREAD());
+
+ // Atomically check if we can transition from QUEUED to EXECUTING
rb_atomic_t expected = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED;
if (RUBY_ATOMIC_CAS(blocking_operation->status, expected, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING) != expected) {
// Already cancelled or in wrong state
@@ -1124,25 +1129,33 @@ rb_fiber_scheduler_blocking_operation_cancel(rb_fiber_scheduler_blocking_operati
rb_atomic_t current_state = RUBY_ATOMIC_LOAD(blocking_operation->status);
- switch (current_state) {
+ switch (current_state) {
case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED:
- // Work hasn't started - just mark as cancelled
+ // Work hasn't started - just mark as cancelled:
if (RUBY_ATOMIC_CAS(blocking_operation->status, current_state, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED) == current_state) {
- return 0; // Successfully cancelled before execution
+ // Successfully cancelled before execution:
+ return 0;
}
// Fall through if state changed between load and CAS
case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING:
// Work is running - mark cancelled AND call unblock function
- RUBY_ATOMIC_SET(blocking_operation->status, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED);
- if (blocking_operation->unblock_function) {
+ if (RUBY_ATOMIC_CAS(blocking_operation->status, current_state, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED) != current_state) {
+ // State changed between load and CAS - operation may have completed:
+ return 0;
+ }
+ // Otherwise, we successfully marked it as cancelled, so we can call the unblock function:
+ rb_unblock_function_t *unblock_function = blocking_operation->unblock_function;
+ if (unblock_function) {
+ RUBY_ASSERT(unblock_function != (rb_unblock_function_t *)-1 && "unblock_function is still sentinel value -1, should have been resolved earlier");
blocking_operation->unblock_function(blocking_operation->data2);
}
- return 1; // Cancelled during execution (unblock function called)
+ // Cancelled during execution (unblock function called):
+ return 1;
case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED:
case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED:
- // Already finished or cancelled
+ // Already finished or cancelled:
return 0;
}
diff --git a/shape.c b/shape.c
index c50147124a..06dcb8d610 100644
--- a/shape.c
+++ b/shape.c
@@ -1550,6 +1550,7 @@ Init_default_shapes(void)
RUBY_ASSERT(root_with_obj_id->edge_name == id_object_id);
RUBY_ASSERT(root_with_obj_id->next_field_index == 1);
RUBY_ASSERT(!(raw_shape_id(root_with_obj_id) & SHAPE_ID_HAS_IVAR_MASK));
+ (void)root_with_obj_id;
}
void
diff --git a/shape.h b/shape.h
index 35d28b41ed..b23fda4e29 100644
--- a/shape.h
+++ b/shape.h
@@ -136,8 +136,6 @@ RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id)
{
RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj));
RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_class_fields));
- RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id));
-
#if RBASIC_SHAPE_ID_FIELD
RBASIC(obj)->shape_id = (VALUE)shape_id;
#else
@@ -145,6 +143,7 @@ RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id)
RBASIC(obj)->flags &= SHAPE_FLAG_MASK;
RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT);
#endif
+ RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id));
}
static inline rb_shape_t *
@@ -343,6 +342,34 @@ rb_shape_obj_has_ivars(VALUE obj)
return rb_shape_has_ivars(RBASIC_SHAPE_ID(obj));
}
+static inline bool
+rb_shape_has_fields(shape_id_t shape_id)
+{
+ return shape_id & (SHAPE_ID_OFFSET_MASK | SHAPE_ID_FL_TOO_COMPLEX);
+}
+
+static inline bool
+rb_shape_obj_has_fields(VALUE obj)
+{
+ return rb_shape_has_fields(RBASIC_SHAPE_ID(obj));
+}
+
+static inline bool
+rb_obj_exivar_p(VALUE obj)
+{
+ switch (TYPE(obj)) {
+ case T_NONE:
+ case T_OBJECT:
+ case T_CLASS:
+ case T_MODULE:
+ case T_IMEMO:
+ return false;
+ default:
+ break;
+ }
+ return rb_shape_obj_has_fields(obj);
+}
+
// For ext/objspace
RUBY_SYMBOL_EXPORT_BEGIN
typedef void each_shape_callback(shape_id_t shape_id, void *data);
diff --git a/st.c b/st.c
index f11e9efaf9..70da7daf83 100644
--- a/st.c
+++ b/st.c
@@ -1495,7 +1495,16 @@ st_update(st_table *tab, st_data_t key,
value = entry->record;
}
old_key = key;
+
+ unsigned int rebuilds_num = tab->rebuilds_num;
+
retval = (*func)(&key, &value, arg, existing);
+
+ // We need to make sure that the callback didn't cause a table rebuild
+ // Ideally we would make sure no operations happened
+ assert(rebuilds_num == tab->rebuilds_num);
+ (void)rebuilds_num;
+
switch (retval) {
case ST_CONTINUE:
if (! existing) {
diff --git a/string.c b/string.c
index 6ab768c244..d9ffb29b8e 100644
--- a/string.c
+++ b/string.c
@@ -485,7 +485,7 @@ build_fstring(VALUE str, struct fstr_update_arg *arg)
RUBY_ASSERT(RB_TYPE_P(str, T_STRING));
RUBY_ASSERT(OBJ_FROZEN(str));
RUBY_ASSERT(!FL_TEST_RAW(str, STR_FAKESTR));
- RUBY_ASSERT(!FL_TEST_RAW(str, FL_EXIVAR));
+ RUBY_ASSERT(!rb_obj_exivar_p(str));
RUBY_ASSERT(RBASIC_CLASS(str) == rb_cString);
RUBY_ASSERT(!rb_objspace_garbage_object_p(str));
diff --git a/test/ruby/test_object_id.rb b/test/ruby/test_object_id.rb
index 018cc81496..9c0099517b 100644
--- a/test/ruby/test_object_id.rb
+++ b/test/ruby/test_object_id.rb
@@ -243,35 +243,4 @@ class TestObjectIdRactor < Test::Unit::TestCase
assert_equal object_id, obj.object_id
end;
end
-
- def test_object_id_race_free_with_stress_compact
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
- begin;
- Warning[:experimental] = false
- class MyClass
- attr_reader :a, :b, :c
- def initialize
- @a = @b = @c = nil
- end
- end
- N = 20
- objs = Ractor.make_shareable(N.times.map { MyClass.new })
-
- GC.stress = true
- GC.auto_compact = true if GC.respond_to?(:auto_compact=)
-
- results = 4.times.map{
- Ractor.new(objs) { |objs|
- vars = []
- ids = []
- objs.each do |obj|
- vars << obj.a << obj.b << obj.c
- ids << obj.object_id
- end
- [vars, ids]
- }
- }.map(&:value)
- assert_equal 1, results.uniq.size
- end;
- end
end
diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb
index 49fec2d40e..d8d0a1a393 100644
--- a/test/ruby/test_variable.rb
+++ b/test/ruby/test_variable.rb
@@ -407,6 +407,19 @@ class TestVariable < Test::Unit::TestCase
}
end
+ def test_exivar_resize_with_compaction_stress
+ objs = 10_000.times.map do
+ ExIvar.new
+ end
+ EnvUtil.under_gc_compact_stress do
+ 10.times do
+ x = ExIvar.new
+ x.instance_variable_set(:@resize, 1)
+ x
+ end
+ end
+ end
+
def test_local_variables_with_kwarg
bug11674 = '[ruby-core:71437] [Bug #11674]'
v = with_kwargs_11(v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8,v9:9,v10:10,v11:11)
diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb
index 165990dd64..4b85d43291 100644
--- a/test/socket/test_socket.rb
+++ b/test/socket/test_socket.rb
@@ -937,6 +937,32 @@ class TestSocket < Test::Unit::TestCase
RUBY
end
+ def test_tcp_socket_open_timeout
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ if family == Socket::AF_INET6
+ sleep
+ else
+ [Addrinfo.tcp("127.0.0.1", 12345)]
+ end
+ end
+
+ assert_raise(Errno::ETIMEDOUT) do
+ Socket.tcp("localhost", 12345, open_timeout: 0.01)
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_open_timeout_with_other_timeouts
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ assert_raise(ArgumentError) do
+ Socket.tcp("localhost", 12345, open_timeout: 0.01, resolv_timout: 0.01)
+ end
+ RUBY
+ end
+
def test_tcp_socket_one_hostname_resolution_succeeded_at_least
opts = %w[-rsocket -W1]
assert_separately opts, <<~RUBY
diff --git a/thread.c b/thread.c
index 232c677382..41bd6c9ec6 100644
--- a/thread.c
+++ b/thread.c
@@ -1540,6 +1540,29 @@ blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region)
#endif
}
+/*
+ * Resolve sentinel unblock function values to their actual function pointers
+ * and appropriate data2 values. This centralizes the logic for handling
+ * RUBY_UBF_IO and RUBY_UBF_PROCESS sentinel values.
+ *
+ * @param unblock_function Pointer to unblock function pointer (modified in place)
+ * @param data2 Pointer to data2 pointer (modified in place)
+ * @param thread Thread context for resolving data2 when needed
+ * @return true if sentinel values were resolved, false otherwise
+ */
+bool
+rb_thread_resolve_unblock_function(rb_unblock_function_t **unblock_function, void **data2, struct rb_thread_struct *thread)
+{
+ rb_unblock_function_t *ubf = *unblock_function;
+
+ if ((ubf == RUBY_UBF_IO) || (ubf == RUBY_UBF_PROCESS)) {
+ *unblock_function = ubf_select;
+ *data2 = thread;
+ return true;
+ }
+ return false;
+}
+
void *
rb_nogvl(void *(*func)(void *), void *data1,
rb_unblock_function_t *ubf, void *data2,
@@ -1566,11 +1589,9 @@ rb_nogvl(void *(*func)(void *), void *data1,
bool is_main_thread = vm->ractor.main_thread == th;
int saved_errno = 0;
- if ((ubf == RUBY_UBF_IO) || (ubf == RUBY_UBF_PROCESS)) {
- ubf = ubf_select;
- data2 = th;
- }
- else if (ubf && rb_ractor_living_thread_num(th->ractor) == 1 && is_main_thread) {
+ rb_thread_resolve_unblock_function(&ubf, &data2, th);
+
+ if (ubf && rb_ractor_living_thread_num(th->ractor) == 1 && is_main_thread) {
if (flags & RB_NOGVL_UBF_ASYNC_SAFE) {
vm->ubf_async_safe = 1;
}
diff --git a/variable.c b/variable.c
index 93ae6bb8b2..6bd9f69d06 100644
--- a/variable.c
+++ b/variable.c
@@ -1255,20 +1255,23 @@ rb_mark_generic_ivar(VALUE obj)
void
rb_free_generic_ivar(VALUE obj)
{
- st_data_t key = (st_data_t)obj, value;
+ if (rb_obj_exivar_p(obj)) {
+ st_data_t key = (st_data_t)obj, value;
- bool too_complex = rb_shape_obj_too_complex_p(obj);
+ bool too_complex = rb_shape_obj_too_complex_p(obj);
- RB_VM_LOCKING() {
- if (st_delete(generic_fields_tbl_no_ractor_check(obj), &key, &value)) {
- struct gen_fields_tbl *fields_tbl = (struct gen_fields_tbl *)value;
+ RB_VM_LOCKING() {
+ if (st_delete(generic_fields_tbl_no_ractor_check(obj), &key, &value)) {
+ struct gen_fields_tbl *fields_tbl = (struct gen_fields_tbl *)value;
- if (UNLIKELY(too_complex)) {
- st_free_table(fields_tbl->as.complex.table);
- }
+ if (UNLIKELY(too_complex)) {
+ st_free_table(fields_tbl->as.complex.table);
+ }
- xfree(fields_tbl);
+ xfree(fields_tbl);
+ }
}
+ RBASIC_SET_SHAPE_ID(obj, ROOT_SHAPE_ID);
}
}
@@ -1325,7 +1328,7 @@ rb_obj_field_get(VALUE obj, shape_id_t target_shape_id)
fields_hash = ROBJECT_FIELDS_HASH(obj);
break;
default:
- RUBY_ASSERT(FL_TEST_RAW(obj, FL_EXIVAR));
+ RUBY_ASSERT(rb_obj_exivar_p(obj));
struct gen_fields_tbl *fields_tbl = NULL;
rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl);
RUBY_ASSERT(fields_tbl);
@@ -1356,7 +1359,7 @@ rb_obj_field_get(VALUE obj, shape_id_t target_shape_id)
fields = ROBJECT_FIELDS(obj);
break;
default:
- RUBY_ASSERT(FL_TEST_RAW(obj, FL_EXIVAR));
+ RUBY_ASSERT(rb_obj_exivar_p(obj));
struct gen_fields_tbl *fields_tbl = NULL;
rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl);
RUBY_ASSERT(fields_tbl);
@@ -1434,7 +1437,7 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
}
default:
shape_id = RBASIC_SHAPE_ID(obj);
- if (FL_TEST_RAW(obj, FL_EXIVAR)) {
+ if (rb_obj_exivar_p(obj)) {
struct gen_fields_tbl *fields_tbl;
rb_gen_fields_tbl_get(obj, id, &fields_tbl);
@@ -1542,13 +1545,18 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef)
RUBY_ASSERT(removed_shape_id != INVALID_SHAPE_ID);
- attr_index_t new_fields_count = RSHAPE_LEN(next_shape_id);
-
attr_index_t removed_index = RSHAPE_INDEX(removed_shape_id);
val = fields[removed_index];
- size_t trailing_fields = new_fields_count - removed_index;
- MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields);
+ attr_index_t new_fields_count = RSHAPE_LEN(next_shape_id);
+ if (new_fields_count) {
+ size_t trailing_fields = new_fields_count - removed_index;
+
+ MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields);
+ }
+ else {
+ rb_free_generic_ivar(obj);
+ }
if (RB_TYPE_P(obj, T_OBJECT) &&
!RB_FL_TEST_RAW(obj, ROBJECT_EMBED) &&
@@ -1810,56 +1818,40 @@ general_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val, void *data,
struct gen_fields_lookup_ensure_size {
VALUE obj;
ID id;
- struct gen_fields_tbl *fields_tbl;
shape_id_t shape_id;
bool resize;
};
-static int
-generic_fields_lookup_ensure_size(st_data_t *k, st_data_t *v, st_data_t u, int existing)
-{
- ASSERT_vm_locking();
-
- struct gen_fields_lookup_ensure_size *fields_lookup = (struct gen_fields_lookup_ensure_size *)u;
- struct gen_fields_tbl *fields_tbl = existing ? (struct gen_fields_tbl *)*v : NULL;
-
- if (!existing || fields_lookup->resize) {
- if (existing) {
- RUBY_ASSERT(RSHAPE_TYPE_P(fields_lookup->shape_id, SHAPE_IVAR) || RSHAPE_TYPE_P(fields_lookup->shape_id, SHAPE_OBJ_ID));
- RUBY_ASSERT(RSHAPE_CAPACITY(RSHAPE_PARENT(fields_lookup->shape_id)) < RSHAPE_CAPACITY(fields_lookup->shape_id));
- }
- else {
- FL_SET_RAW((VALUE)*k, FL_EXIVAR);
- }
-
- fields_tbl = gen_fields_tbl_resize(fields_tbl, RSHAPE_CAPACITY(fields_lookup->shape_id));
- *v = (st_data_t)fields_tbl;
- }
-
- RUBY_ASSERT(FL_TEST((VALUE)*k, FL_EXIVAR));
-
- fields_lookup->fields_tbl = fields_tbl;
- if (fields_lookup->shape_id) {
- rb_obj_set_shape_id(fields_lookup->obj, fields_lookup->shape_id);
- }
-
- return ST_CONTINUE;
-}
-
static VALUE *
generic_ivar_set_shape_fields(VALUE obj, void *data)
{
RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj));
struct gen_fields_lookup_ensure_size *fields_lookup = data;
+ struct gen_fields_tbl *fields_tbl = NULL;
+ // We can't use st_update, since when resizing the fields table GC can
+ // happen, which will modify the st_table and may rebuild it
RB_VM_LOCKING() {
- st_update(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, generic_fields_lookup_ensure_size, (st_data_t)fields_lookup);
- }
+ st_table *tbl = generic_fields_tbl(obj, fields_lookup->id, false);
+ int existing = st_lookup(tbl, (st_data_t)obj, (st_data_t *)&fields_tbl);
+
+ if (!existing || fields_lookup->resize) {
+ if (existing) {
+ RUBY_ASSERT(RSHAPE_TYPE_P(fields_lookup->shape_id, SHAPE_IVAR) || RSHAPE_TYPE_P(fields_lookup->shape_id, SHAPE_OBJ_ID));
+ RUBY_ASSERT(RSHAPE_CAPACITY(RSHAPE_PARENT(fields_lookup->shape_id)) < RSHAPE_CAPACITY(fields_lookup->shape_id));
+ }
- FL_SET_RAW(obj, FL_EXIVAR);
+ fields_tbl = gen_fields_tbl_resize(fields_tbl, RSHAPE_CAPACITY(fields_lookup->shape_id));
+ st_insert(tbl, (st_data_t)obj, (st_data_t)fields_tbl);
+ }
+
+ if (fields_lookup->shape_id) {
+ rb_obj_set_shape_id(fields_lookup->obj, fields_lookup->shape_id);
+ }
+ }
- return fields_lookup->fields_tbl->as.shape.fields;
+ return fields_tbl->as.shape.fields;
}
static void
@@ -1882,7 +1874,6 @@ static shape_id_t
generic_ivar_set_transition_too_complex(VALUE obj, void *_data)
{
shape_id_t new_shape_id = rb_evict_fields_to_hash(obj);
- FL_SET_RAW(obj, FL_EXIVAR);
return new_shape_id;
}
@@ -1899,8 +1890,6 @@ generic_ivar_set_too_complex_table(VALUE obj, void *data)
RB_VM_LOCKING() {
st_insert(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, (st_data_t)fields_tbl);
}
-
- FL_SET_RAW(obj, FL_EXIVAR);
}
RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
@@ -2341,8 +2330,8 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj)
rb_check_frozen(dest);
- if (!FL_TEST(obj, FL_EXIVAR)) {
- goto clear;
+ if (!rb_obj_exivar_p(obj)) {
+ return;
}
unsigned long src_num_ivs = rb_ivar_count(obj);
@@ -2356,8 +2345,6 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj)
if (gen_fields_tbl_count(obj, obj_fields_tbl) == 0)
goto clear;
- FL_SET(dest, FL_EXIVAR);
-
if (rb_shape_too_complex_p(src_shape_id)) {
rb_shape_copy_complex_ivars(dest, obj, src_shape_id, obj_fields_tbl->as.complex.table);
return;
@@ -2381,7 +2368,6 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj)
if (!RSHAPE_LEN(dest_shape_id)) {
rb_obj_set_shape_id(dest, dest_shape_id);
- FL_UNSET(dest, FL_EXIVAR);
return;
}
@@ -2406,25 +2392,16 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj)
return;
clear:
- if (FL_TEST(dest, FL_EXIVAR)) {
- RBASIC_SET_SHAPE_ID(dest, ROOT_SHAPE_ID);
- rb_free_generic_ivar(dest);
- FL_UNSET(dest, FL_EXIVAR);
- }
+ rb_free_generic_ivar(dest);
}
void
rb_replace_generic_ivar(VALUE clone, VALUE obj)
{
- RUBY_ASSERT(FL_TEST(obj, FL_EXIVAR));
-
RB_VM_LOCKING() {
st_data_t fields_tbl, obj_data = (st_data_t)obj;
if (st_delete(generic_fields_tbl_, &obj_data, &fields_tbl)) {
- FL_UNSET_RAW(obj, FL_EXIVAR);
-
st_insert(generic_fields_tbl_, (st_data_t)clone, fields_tbl);
- FL_SET_RAW(clone, FL_EXIVAR);
}
else {
rb_bug("unreachable");
@@ -2456,7 +2433,7 @@ rb_field_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg,
}
break;
default:
- if (FL_TEST_RAW(obj, FL_EXIVAR)) {
+ if (rb_obj_exivar_p(obj)) {
gen_fields_each(obj, func, arg, ivar_only);
}
break;
@@ -2492,7 +2469,7 @@ rb_ivar_count(VALUE obj)
return RBASIC_FIELDS_COUNT(fields_obj);
}
default:
- if (FL_TEST(obj, FL_EXIVAR)) {
+ if (rb_obj_exivar_p(obj)) {
struct gen_fields_tbl *fields_tbl;
if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) {
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 5192ee2d82..7efcdba8a4 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -1258,7 +1258,7 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call
break;
}
default:
- if (FL_TEST_RAW(obj, FL_EXIVAR)) {
+ if (rb_obj_exivar_p(obj)) {
struct gen_fields_tbl *fields_tbl;
rb_gen_fields_tbl_get(obj, id, &fields_tbl);
ivar_list = fields_tbl->as.shape.fields;
diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs
index a1c7464805..1d7ffca165 100644
--- a/yjit/src/cruby_bindings.inc.rs
+++ b/yjit/src/cruby_bindings.inc.rs
@@ -225,10 +225,11 @@ pub const RUBY_FL_PROMOTED: ruby_fl_type = 32;
pub const RUBY_FL_UNUSED6: ruby_fl_type = 64;
pub const RUBY_FL_FINALIZE: ruby_fl_type = 128;
pub const RUBY_FL_TAINT: ruby_fl_type = 0;
+pub const RUBY_FL_EXIVAR: ruby_fl_type = 0;
pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256;
pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0;
pub const RUBY_FL_UNUSED9: ruby_fl_type = 512;
-pub const RUBY_FL_EXIVAR: ruby_fl_type = 1024;
+pub const RUBY_FL_UNUSED10: ruby_fl_type = 1024;
pub const RUBY_FL_FREEZE: ruby_fl_type = 2048;
pub const RUBY_FL_USER0: ruby_fl_type = 4096;
pub const RUBY_FL_USER1: ruby_fl_type = 8192;
diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs
index bcc8f48c37..5fb5c2ec02 100644
--- a/zjit/src/cruby_bindings.inc.rs
+++ b/zjit/src/cruby_bindings.inc.rs
@@ -101,10 +101,11 @@ pub const RUBY_FL_PROMOTED: ruby_fl_type = 32;
pub const RUBY_FL_UNUSED6: ruby_fl_type = 64;
pub const RUBY_FL_FINALIZE: ruby_fl_type = 128;
pub const RUBY_FL_TAINT: ruby_fl_type = 0;
+pub const RUBY_FL_EXIVAR: ruby_fl_type = 0;
pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256;
pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0;
pub const RUBY_FL_UNUSED9: ruby_fl_type = 512;
-pub const RUBY_FL_EXIVAR: ruby_fl_type = 1024;
+pub const RUBY_FL_UNUSED10: ruby_fl_type = 1024;
pub const RUBY_FL_FREEZE: ruby_fl_type = 2048;
pub const RUBY_FL_USER0: ruby_fl_type = 4096;
pub const RUBY_FL_USER1: ruby_fl_type = 8192;