This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git


The following commit(s) were added to refs/heads/main by this push:
     new 963cbb04 [TEST][RUST] Register the reflection test types before any 
test reads… (#862)
963cbb04 is described below

commit 963cbb04bd94aaaf4e2053a243c31b3fba73aa3b
Author: Linzhang Li <[email protected]>
AuthorDate: Thu Oct 22 13:28:14 2026 -0400

    [TEST][RUST] Register the reflection test types before any test reads… 
(#862)
    
    ## Summary
    
    Fix a flaky crash in the Rust test `test_object_reflection` (added in
    #859).
    It failed the Windows job of the `main` CI run
    
    
[39440570092](https://github.com/apache/tvm-ffi/actions/runs/39440570092/job/118891173316):
    
    ```text
    Exception caught during TVMFFIGetTypeInfo:
    Check failed: (entry != nullptr) is false: Cannot find type info for 
type_index=139
    ```
    
    ## Cause
    
    `#[type_register]` types register on first use. In
    `test_object_reflection.rs`
    that first use happens inside the tests, which cargo runs on several
    threads,
    so one test registers a type while another reads the type table
    (`TVMFFIGetTypeInfo`, `is_instance_of`, `ffi.StructuralEqual`, ...).
    
    `TYPE_REGISTRATION` keeps registrations from running at the same time,
    but it
    does not block readers, and the runtime does not lock its type table.
    Each
    type registered in the dynamic range grows `type_table_`
    (`src/ffi/object.cc`):
    the vector reallocates, moves every entry out of the old buffer and
    frees it.
    A reader still on the old buffer sees a null entry (the error above,
    after
    which the process exits) or freed memory (a segfault, seen locally in
    `is_instance_of`, `StructuralEqual::Equal` and `StructuralHash::Hash`).
    
    ## Changes
    
    - `register_types()` registers the four types of the file (`PointObj`,
    `Point3Obj`, `CounterObj`, `LeafObj`) once, behind a `std::sync::Once`,
    and
    each test calls it first. All registrations then finish before any test
      reads the table. The tests still run in parallel.
---
 rust/tvm-ffi/tests/test_object_reflection.rs | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/rust/tvm-ffi/tests/test_object_reflection.rs 
b/rust/tvm-ffi/tests/test_object_reflection.rs
index 74054b05..0a9ee0c0 100644
--- a/rust/tvm-ffi/tests/test_object_reflection.rs
+++ b/rust/tvm-ffi/tests/test_object_reflection.rs
@@ -115,8 +115,22 @@ fn call(name: &str, args: &[AnyView]) -> Any {
         .unwrap()
 }
 
+// A type registers on first use, and the runtime does not guard its type table
+// against readers on other threads, so each test registers every type here
+// before it reads the table.
+fn register_types() {
+    static REGISTERED: std::sync::Once = std::sync::Once::new();
+    REGISTERED.call_once(|| {
+        PointObj::type_index();
+        Point3Obj::type_index();
+        CounterObj::type_index();
+        no_imports::LeafObj::type_index();
+    });
+}
+
 #[test]
 fn test_reflected_fields_are_registered_with_the_type() {
+    register_types();
     let info = type_info::<PointObj>();
     let names: Vec<&str> = fields(info).iter().map(|f| 
f.name.as_str()).collect();
     assert_eq!(names, ["x", "y", "label"]);
@@ -167,6 +181,7 @@ fn test_reflected_fields_are_registered_with_the_type() {
 
 #[test]
 fn test_reflected_fields_read_and_write_through_the_c_abi() {
+    register_types();
     let p3 = point3(3, 4);
     let x = FieldGetter::new(Point3Obj::type_index(), "x").unwrap();
     let z = FieldGetter::new(Point3Obj::type_index(), "z").unwrap();
@@ -201,6 +216,7 @@ fn test_reflected_fields_read_and_write_through_the_c_abi() 
{
 
 #[test]
 fn test_cpp_structural_equality_and_hash_see_reflected_fields() {
+    register_types();
     let equal = |a: &Point, b: &Point| -> bool {
         let (a, b) = (Any::from(a.clone()), Any::from(b.clone()));
         let args = [
@@ -233,6 +249,7 @@ fn 
test_cpp_structural_equality_and_hash_see_reflected_fields() {
 
 #[test]
 fn test_repr_and_deep_copy_see_reflected_fields() {
+    register_types();
     let p = Any::from(point(5, 1.5, "hidden"));
     let repr = tvm_ffi::String::try_from(call("ffi.ReprPrint", 
&[AnyView::from(&p)])).unwrap();
     assert!(repr.as_str().contains("x=5"), "{}", repr.as_str());
@@ -255,6 +272,7 @@ fn test_repr_and_deep_copy_see_reflected_fields() {
 
 #[test]
 fn test_shallow_copy_and_finality_are_type_attributes() {
+    register_types();
     let original = point(1, 2.0, "p");
     let shallow_copy =
         Function::try_from(get_type_attr(PointObj::type_index(), 
"__ffi_shallow_copy__").unwrap())
@@ -307,6 +325,7 @@ struct Counter {
 
 #[test]
 fn test_types_without_reflect_register_no_reflection() {
+    register_types();
     let info = type_info::<CounterObj>();
     assert_eq!(info.num_fields, 0);
     assert!(info.metadata.is_null());
@@ -343,6 +362,7 @@ mod no_imports {
 
 #[test]
 fn test_derives_need_nothing_in_scope() {
+    register_types();
     let leaf = no_imports::leaf(9);
     let w = FieldGetter::new(no_imports::LeafObj::type_index(), "w").unwrap();
     assert_eq!(w.get::<_, i64>(&*leaf.data).unwrap(), 9);

Reply via email to