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);