Skip to content

refactor(instrumentation-build): use generic runtime types - #466

Merged
rapids-bot[bot] merged 2 commits into
rapidsai:mainfrom
johanpel:instrumentation-paths-1
Jul 31, 2026
Merged

refactor(instrumentation-build): use generic runtime types#466
rapids-bot[bot] merged 2 commits into
rapidsai:mainfrom
johanpel:instrumentation-paths-1

Conversation

@johanpel

Copy link
Copy Markdown
Contributor

Description

Simplify the generated instrumentation API by replacing per-entity observer and handle types with generic types parameterized by schema markers.

Previously, a model generated types such as:

ServerObserver
ConnectionObserver
ServerHandle
ConnectionHandle
DemoContext

It now exposes a consistent API:

let context: Context<Demo> = Context::try_new(exporter)?;
let observer: Observer<Connection> = context.observer::<Connection>();
let handle: Handle<Connection> = observer.handle();

This avoids collisions between generated helper names and valid schema types such as QueryObserver or QueryHandle. Shared behavior now lives in quent-instrumentation behind ContextInner, ObserverInner, and HandleInner; generated code retains the model and entity markers, typed observer storage, generic handle wrapper, and entity-specific event methods.

Observer lookup uses generated typed fields and ObserverAccess<E>, retaining exactly one observer transport per entity and context without runtime downcasts or tuple indices.

This PR intentionally continues rejecting qualified schema paths. Support for namespace modules and qualified types follows in the stacked instrumentation-paths-2 PR.

Related Issues

Part of #442.

Written by Codex.

@johanpel
johanpel force-pushed the instrumentation-paths-1 branch from aa3333a to 42bb324 Compare July 27, 2026 12:18
@johanpel
johanpel marked this pull request as ready for review July 27, 2026 12:22
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 814c74a4-5d4d-487b-bd7b-09789531f6f3

📥 Commits

Reviewing files that changed from the base of the PR and between dde7df7 and 3211c70.

📒 Files selected for processing (1)
  • crates/instrumentation-build/src/runtime/context.rs

📝 Walkthrough

Walkthrough

The instrumentation runtime now separates inner runtime state from model contexts, adds typed entity and observer abstractions, updates generated code and bindings, and migrates benchmarks, tests, examples, macros, and NVTX integration to the new APIs.

Changes

Instrumentation runtime and generated API

Layer / File(s) Summary
Inner context and observer runtime
crates/instrumentation/src/context.rs, crates/instrumentation/src/observer.rs
Introduces ContextInner, ObserverInner, and the Runtime wrapper while preserving runtime resolution, forwarding, noop behavior, and flush-on-drop handling.
Typed model, entity, and handle abstractions
crates/instrumentation/src/entity.rs, crates/instrumentation/src/model.rs, crates/instrumentation/src/handle.rs, crates/instrumentation/src/lib.rs
Adds typed model and entity traits, generated observer access, Observer<E>, and HandleInner<E> with entity-reference and once-emission support.
Generated model and entity runtime surface
crates/instrumentation-build/src/runtime/*, crates/instrumentation-build/src/lib.rs, crates/instrumentation-build/example/*
Generates schema models, typed observer storage, shared handles, entity implementations, collision errors, and the updated example API.
Generated bindings and integration migration
crates/codegen/src/*_bridge.rs, crates/model-macros/src/model_macro.rs, crates/model/src/lib.rs, crates/instrumentation/benches/*, crates/instrumentation/tests/*, integrations/nvtx/example/src/lib.rs
Migrates generated bindings, model macros, benchmarks, re-exports, tests, and NVTX capture to ContextInner and the new observer pipeline types.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • rapidsai/quent#390: Both changes extend typed entity-reference and handle generation APIs.
  • rapidsai/quent#449: Both changes update generated-type collision handling and schema-path validation.

Suggested labels: improvement

Suggested reviewers: mbrobbel, dhruv9vats

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main refactor and accurately mentions the move to generic runtime types.
Description check ✅ Passed Covers the required Description and Related Issues sections; Testing and Screenshots are omitted but non-critical.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/instrumentation/src/handle.rs (1)

29-113: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add direct unit tests for the new typed Entity/Observer/Model/Context/HandleInner core. This new public API surface — the central refactor of this PR — is currently only exercised indirectly (codegen string-matching assertions and a non-#[test] example binary), not by #[test]s in quent-instrumentation itself.

  • crates/instrumentation/src/handle.rs#L29-L113: add tests for emit_once's bit tracking and HandleError::OnceAlreadyEmitted, plus the new as_entity_ref*/as_any_entity_ref* builders.
  • crates/instrumentation/src/entity.rs#L37-L56: add a test that Observer::handle_with_id(id) produces a handle whose uuid() equals id, and that handle() produces fresh, distinct ids.
  • crates/instrumentation/src/model.rs#L46-L89: add a test constructing a minimal Context<M> (a hand-written Model impl, or reuse a generated test schema) and exercising try_new/observer/id.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation/src/handle.rs` around lines 29 - 113, Add direct
#[test] coverage for the new core APIs: in
crates/instrumentation/src/handle.rs:29-113, test emit_once bit tracking,
OnceAlreadyEmitted errors, and all as_entity_ref*/as_any_entity_ref* builders;
in crates/instrumentation/src/entity.rs:37-56, verify handle_with_id preserves
the supplied UUID and handle produces distinct UUIDs; in
crates/instrumentation/src/model.rs:46-89, construct a minimal Context<M> and
exercise try_new, observer, and id.

Source: Coding guidelines

🟡 Other comments (1)
crates/instrumentation-build/src/lib.rs-146-152 (1)

146-152: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the public error variant.

GeneratedTypeCollision is externally matchable, but only its fields are documented. Add Rustdoc stating when generation returns this error.

Proposed fix
+    /// Returned when a generated observer type name collides with a schema type.
     #[error("generated observer type `{generated}` conflicts with schema type `{schema_path}`")]
     GeneratedTypeCollision {

As per path instructions, “New pub items need a doc comment and justified visibility.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation-build/src/lib.rs` around lines 146 - 152, Add a
Rustdoc comment directly above the public GeneratedTypeCollision variant
explaining that generation returns it when a generated observer type name
conflicts with a schema type name. Keep the existing field documentation and
visibility unchanged.

Source: Path instructions

🧹 Nitpick comments (3)
crates/instrumentation-build/src/runtime/mod.rs (1)

140-175: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No negative test for GeneratedTypeCollision.

Only the happy path is asserted here; there's no test exercising a schema whose entity/record name collides with {Schema}Observers (or the other reserved names raised in the consolidated comment). Consider adding one alongside generates_generic_instrumentation_api.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation-build/src/runtime/mod.rs` around lines 140 - 175, Add
a negative test alongside generates_generic_instrumentation_api that builds a
schema containing an entity or record named after the generated reserved types,
including {Schema}Observers and other names covered by GeneratedTypeCollision.
Assert schema/runtime generation rejects the collision with the expected
GeneratedTypeCollision error, while leaving the existing happy-path test
unchanged.

Source: Coding guidelines

crates/instrumentation-build/src/runtime/context.rs (2)

17-54: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Collision detection for generator-reserved identifiers is incomplete. observer_storage's GeneratedTypeCollision check only guards the {Schema}Observers name, but entity_types/reexports inject several more bare names into the same generated module scope that can equally collide with a user-named entity or record, producing a confusing raw compiler error (duplicate definition/ambiguous import) instead of the intended actionable diagnostic.

  • crates/instrumentation-build/src/runtime/context.rs#L17-L54: extend the reserved-name check to also cover the schema model marker name (model_ident) and the names in reexports(), e.g. via a shared constant list of reserved idents checked once against all entity/record paths.
  • crates/instrumentation-build/src/runtime/mod.rs#L51-L89: entity_types's literal Handle ident and reexports()'s bare names (Context, Observer, AnyEntity, EntityRef, Event, HandleError, DynamicAttributes, Uuid) should participate in the same collision check.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation-build/src/runtime/context.rs` around lines 17 - 54,
The reserved-name collision check spanning
crates/instrumentation-build/src/runtime/context.rs:17-54 and
crates/instrumentation-build/src/runtime/mod.rs:51-89 is incomplete. Define a
shared reserved-identifier set covering observers_ident, model_ident,
entity_types’ Handle, and every bare name emitted by reexports (Context,
Observer, AnyEntity, EntityRef, Event, HandleError, DynamicAttributes, and
Uuid), then have observer_storage validate all entity and record paths against
it and report GeneratedTypeCollision consistently.

Source: Path instructions


56-151: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Entity observers are built sequentially, not concurrently, unlike sibling bridges.

Each field of the generated struct literal is a separate context.observer::<EventTy>(...).await?; Rust evaluates struct-literal field initializers left-to-right, so with N entities this serializes N independent exporter-construction futures. crates/codegen/src/cxx_bridge.rs's emit_context_bridge and crates/codegen/src/pyo3_bridge.rs's emit_context build the analogous per-entity observers concurrently via #q::tokio::try_join!(...) for the same conceptual step. For schemas with several entities and I/O-bound exporters (e.g. network-backed exporters), this makes schema-driven context construction slower than the macro/FFI-bridge paths without a clear reason for the difference.

♻️ Suggested direction: build via `tokio::try_join!` instead of sequential struct-literal awaits
 fn observer_storage_initializer(schema: &Schema, active: bool) -> TokenStream {
     let storage = observers_ident(schema);
-    let entity_fields = schema.entities().map(|entity| {
-        let field = entity_observer_field(entity);
-        let entity_ty = marker_ident(entity);
-        let event_ty = event_ident(entity);
-        let observer = if active {
-            quote! {
-                context
-                    .observer::<`#event_ty`>(::core::clone::Clone::clone(options))
-                    .await?
-            }
-        } else {
-            quote! {
-                ::quent_instrumentation::ObserverInner::<`#event_ty`>::noop()
-            }
-        };
-        quote! {
-            `#field`: ::quent_instrumentation::Observer::<`#entity_ty`>::new(
-                ::std::sync::Arc::new(`#observer`),
-            )
-        }
-    });
-    quote! {
-        `#storage` {
-            #(`#entity_fields`,)*
-        }
-    }
+    let fields: Vec<_> = schema.entities().map(entity_observer_field).collect();
+    let entity_tys: Vec<_> = schema.entities().map(marker_ident).collect();
+    let event_tys: Vec<_> = schema.entities().map(event_ident).collect();
+    if active {
+        quote! {
+            {
+                let (#(`#fields`,)*) = ::quent_instrumentation::tokio::try_join!(
+                    #(context.observer::<`#event_tys`>(::core::clone::Clone::clone(options)),)*
+                )?;
+                `#storage` {
+                    #(`#fields`: ::quent_instrumentation::Observer::<`#entity_tys`>::new(
+                        ::std::sync::Arc::new(`#fields`),
+                    ),)*
+                }
+            }
+        }
+    } else {
+        quote! {
+            `#storage` {
+                #(`#fields`: ::quent_instrumentation::Observer::<`#entity_tys`>::new(
+                    ::std::sync::Arc::new(::quent_instrumentation::ObserverInner::<`#event_tys`>::noop()),
+                ),)*
+            }
+        }
+    }
 }

This requires tokio::try_join! (or equivalent) to be accessible through the quent-instrumentation crate, mirroring how cxx_bridge.rs/pyo3_bridge.rs reference #q::tokio::try_join!.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation-build/src/runtime/context.rs` around lines 56 - 151,
Update observer_storage_initializer and its active path in schema_model to
construct all entity observers concurrently with the instrumentation crate’s
tokio::try_join! (or equivalent), rather than awaiting each observer in the
generated struct literal sequentially. Preserve the existing noop initialization
for inactive observers and propagate any exporter-construction error while
assigning each joined result to the corresponding entity field.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/instrumentation/src/context.rs`:
- Around line 92-95: The no-op ContextInner::noop currently leaves block_on
without a runtime, causing caller-visible panics for pending futures. Update
noop and the public block_on path to provide a driveable no-op runtime or
propagate a defined Result error instead of reaching unreachable!, and revise
the associated test to verify the supported pending-future behavior.

---

Outside diff comments:
In `@crates/instrumentation/src/handle.rs`:
- Around line 29-113: Add direct #[test] coverage for the new core APIs: in
crates/instrumentation/src/handle.rs:29-113, test emit_once bit tracking,
OnceAlreadyEmitted errors, and all as_entity_ref*/as_any_entity_ref* builders;
in crates/instrumentation/src/entity.rs:37-56, verify handle_with_id preserves
the supplied UUID and handle produces distinct UUIDs; in
crates/instrumentation/src/model.rs:46-89, construct a minimal Context<M> and
exercise try_new, observer, and id.

---

Other comments:
In `@crates/instrumentation-build/src/lib.rs`:
- Around line 146-152: Add a Rustdoc comment directly above the public
GeneratedTypeCollision variant explaining that generation returns it when a
generated observer type name conflicts with a schema type name. Keep the
existing field documentation and visibility unchanged.

---

Nitpick comments:
In `@crates/instrumentation-build/src/runtime/context.rs`:
- Around line 17-54: The reserved-name collision check spanning
crates/instrumentation-build/src/runtime/context.rs:17-54 and
crates/instrumentation-build/src/runtime/mod.rs:51-89 is incomplete. Define a
shared reserved-identifier set covering observers_ident, model_ident,
entity_types’ Handle, and every bare name emitted by reexports (Context,
Observer, AnyEntity, EntityRef, Event, HandleError, DynamicAttributes, and
Uuid), then have observer_storage validate all entity and record paths against
it and report GeneratedTypeCollision consistently.
- Around line 56-151: Update observer_storage_initializer and its active path in
schema_model to construct all entity observers concurrently with the
instrumentation crate’s tokio::try_join! (or equivalent), rather than awaiting
each observer in the generated struct literal sequentially. Preserve the
existing noop initialization for inactive observers and propagate any
exporter-construction error while assigning each joined result to the
corresponding entity field.

In `@crates/instrumentation-build/src/runtime/mod.rs`:
- Around line 140-175: Add a negative test alongside
generates_generic_instrumentation_api that builds a schema containing an entity
or record named after the generated reserved types, including {Schema}Observers
and other names covered by GeneratedTypeCollision. Assert schema/runtime
generation rejects the collision with the expected GeneratedTypeCollision error,
while leaving the existing happy-path test unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 3f582f6e-8362-482c-b984-20a89e743701

📥 Commits

Reviewing files that changed from the base of the PR and between dace85b and 42bb324.

📒 Files selected for processing (20)
  • crates/codegen/src/cxx_bridge.rs
  • crates/codegen/src/pyo3_bridge.rs
  • crates/instrumentation-build/example/src/main.rs
  • crates/instrumentation-build/src/lib.rs
  • crates/instrumentation-build/src/runtime/context.rs
  • crates/instrumentation-build/src/runtime/handle.rs
  • crates/instrumentation-build/src/runtime/mod.rs
  • crates/instrumentation-build/src/runtime/observer.rs
  • crates/instrumentation/benches/event_emit.rs
  • crates/instrumentation/src/context.rs
  • crates/instrumentation/src/entity.rs
  • crates/instrumentation/src/handle.rs
  • crates/instrumentation/src/lib.rs
  • crates/instrumentation/src/model.rs
  • crates/instrumentation/src/observer.rs
  • crates/instrumentation/tests/collector_roundtrip.rs
  • crates/instrumentation/tests/runtime_flavors.rs
  • crates/model-macros/src/model_macro.rs
  • crates/model/src/lib.rs
  • integrations/nvtx/example/src/lib.rs
💤 Files with no reviewable changes (1)
  • crates/instrumentation-build/src/runtime/observer.rs

Comment thread crates/instrumentation/src/context.rs
Signed-off-by: Johan Peltenburg <johan.peltenburg+code@gmail.com>
@johanpel
johanpel force-pushed the instrumentation-paths-1 branch from 42bb324 to dde7df7 Compare July 30, 2026 06:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/instrumentation-build/src/runtime/mod.rs (1)

149-174: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

No test covers the new GeneratedTypeCollision error path.

Add a case with an entity/record named DemoObservers (or whatever the reserved set ends up being) asserting generate_runtime_types returns GenerateError::GeneratedTypeCollision.

As per coding guidelines, "New Rust components must include accompanying tests", and per path instructions, "Cover new public contracts and error/edge paths".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation-build/src/runtime/mod.rs` around lines 149 - 174,
Extend generates_generic_instrumentation_api with a collision case using an
entity or record named DemoObservers, then assert generate_runtime_types returns
GenerateError::GeneratedTypeCollision. Use the actual reserved generated-type
name set if it differs, and preserve the existing successful-generation
assertions.

Sources: Coding guidelines, Path instructions

🟡 Other comments (2)
crates/instrumentation-build/src/runtime/context.rs-85-97 (1)

85-97: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Entity-less schemas still make options unused. Since SchemaBuilder allows empty schemas, the Some(options) arm will emit an unused binding when there are no entities, which becomes a hard error under -D warnings. Use _options in that case or avoid binding it altogether.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation-build/src/runtime/context.rs` around lines 85 - 97,
Update the exporter match in the generated observer initialization to avoid
binding the Some payload as options when the schema has no entities, preventing
an unused-variable error under -D warnings. Use an ignored binding or otherwise
preserve the existing Some/None behavior without introducing an unused options
symbol.

Source: Coding guidelines

crates/instrumentation/tests/collector_roundtrip.rs-84-84 (1)

84-84: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Propagate setup errors in this test. Return Result from collector_roundtrip.rs and use ? for both ContextInner::try_new(id) and ctx.observer(...) instead of unwrap().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation/tests/collector_roundtrip.rs` at line 84, Update the
test function in collector_roundtrip.rs to return a Result, propagate setup
failures from ContextInner::try_new(id) and ctx.observer(...) with ?, and remove
the corresponding unwrap calls while preserving the existing test behavior.

Source: Path instructions

🧹 Nitpick comments (5)
crates/instrumentation-build/src/runtime/handle.rs (1)

29-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Generated Handle name is duplicated across modules.

raw_ident("Handle") here must stay in sync with the literal Handle written in entity_types (crates/instrumentation-build/src/runtime/mod.rs Lines 56-78). Hoist a single handle_ident() helper in runtime/mod.rs and use it from both sites so the generated type name has one definition.

Also applies to: 124-124

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation-build/src/runtime/handle.rs` at line 29, Centralize
the generated Handle identifier by adding a shared handle_ident() helper in
runtime/mod.rs, then replace the local raw_ident("Handle") construction in the
handle-generation code and the literal Handle usage in entity_types with that
helper. Ensure both generation sites derive the type name from this single
definition.
crates/instrumentation-build/src/runtime/context.rs (1)

123-151: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Observers are built serially; the macro path builds them concurrently.

Each entity's context.observer::<E>(…).await? is awaited in sequence inside one async block, whereas crates/model-macros/src/model_macro.rs (Lines 486-493) uses tokio::try_join! so every exporter is constructed concurrently. For schemas with many entities and non-trivial exporter setup (filesystem/network), context construction latency now scales linearly. Consider emitting a try_join! (or join_all) form here to preserve the existing startup characteristics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation-build/src/runtime/context.rs` around lines 123 - 151,
Update observer_storage_initializer so active observers are initialized
concurrently rather than awaiting each context.observer call serially. Emit a
try_join! or equivalent concurrent future aggregation for all entity observers,
then construct the storage fields from the results while preserving existing
error propagation. Keep the inactive path using ObserverInner::noop() without
unnecessary async work.
crates/instrumentation/src/model.rs (2)

46-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the deliberate field order for Drop safety.

observers is declared before inner so Rust drops M::Observers (flushing each ObserverInner) before inner: ContextInner's Runtime can be torn down — this is what makes ObserverInner::sender()-based 'static producers and in-flight forwarders safe to flush even as the context goes away. This ordering is easy to break silently in a future edit (e.g. reordering fields or adding a field between them). A short comment would prevent that regression.

📝 Suggested comment
 pub struct Context<M: Model> {
+    // Drop order matters: `observers` must be dropped before `inner` so each
+    // `ObserverInner`'s forwarder flushes on the still-alive shared `Runtime`.
     observers: M::Observers,
     inner: ContextInner,
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation/src/model.rs` around lines 46 - 49, Add a concise
comment directly above the fields in Context explaining that observers must
remain declared before inner so Rust drops M::Observers and flushes
ObserverInner instances before ContextInner tears down its Runtime. Make the
ordering requirement explicit, including that future fields must not be inserted
between them.

19-43: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Model::build_observers also returns Box<dyn std::error::Error>.

Same error-typing concern as ContextInner in context.rs; see the consolidated comment at the end of this review.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation/src/model.rs` around lines 19 - 43, Update the
Model::build_observers return type to use the same concrete, thread-safe error
type required by ContextInner instead of Box<dyn std::error::Error>, and adjust
its documentation or callers as needed to preserve error propagation.

Source: Path instructions

crates/instrumentation/src/context.rs (1)

75-152: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

ContextInner fallible APIs use Box<dyn std::error::Error> instead of typed/thiserror errors.

try_new, observer, and (in model.rs) Model::build_observers/Context::try_new all return Result<_, Box<dyn std::error::Error>>. This is a shared root cause across both files; a consolidated note is added at the end of this review covering both sites.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/instrumentation/src/context.rs` around lines 75 - 152, Replace Box<dyn
std::error::Error> in ContextInner::try_new and ContextInner::observer, plus
Model::build_observers and Context::try_new, with the crate’s typed
thiserror-based error type. Add or reuse variants that preserve
runtime-resolution and exporter-construction failures, and propagate those
errors through the existing call paths without changing successful behavior.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/instrumentation-build/src/runtime/context.rs`:
- Around line 17-30: Extract the existing collision logic from observer_storage
into a shared helper that checks every generated type name: the observers_ident
result, Handle, and the generated model marker name emitted by
generate_runtime_types. Compare each reserved name against Pascal-cased record
and entity paths, return GeneratedTypeCollision with the conflicting schema
path, and invoke this helper before generating the runtime types.

In `@crates/instrumentation-build/src/runtime/mod.rs`:
- Around line 70-78: The generated Handle implementation must stop implementing
Deref to HandleInner<E>, which currently exposes the untyped emit API and other
unintended methods. Remove that Deref implementation and explicitly forward only
the curated accessors required by Handle, such as id(), using justified
visibility and doc comments for any new public items; preserve the generated
typed event methods and their once-event ordering contract.

---

Outside diff comments:
In `@crates/instrumentation-build/src/runtime/mod.rs`:
- Around line 149-174: Extend generates_generic_instrumentation_api with a
collision case using an entity or record named DemoObservers, then assert
generate_runtime_types returns GenerateError::GeneratedTypeCollision. Use the
actual reserved generated-type name set if it differs, and preserve the existing
successful-generation assertions.

---

Other comments:
In `@crates/instrumentation-build/src/runtime/context.rs`:
- Around line 85-97: Update the exporter match in the generated observer
initialization to avoid binding the Some payload as options when the schema has
no entities, preventing an unused-variable error under -D warnings. Use an
ignored binding or otherwise preserve the existing Some/None behavior without
introducing an unused options symbol.

In `@crates/instrumentation/tests/collector_roundtrip.rs`:
- Line 84: Update the test function in collector_roundtrip.rs to return a
Result, propagate setup failures from ContextInner::try_new(id) and
ctx.observer(...) with ?, and remove the corresponding unwrap calls while
preserving the existing test behavior.

---

Nitpick comments:
In `@crates/instrumentation-build/src/runtime/context.rs`:
- Around line 123-151: Update observer_storage_initializer so active observers
are initialized concurrently rather than awaiting each context.observer call
serially. Emit a try_join! or equivalent concurrent future aggregation for all
entity observers, then construct the storage fields from the results while
preserving existing error propagation. Keep the inactive path using
ObserverInner::noop() without unnecessary async work.

In `@crates/instrumentation-build/src/runtime/handle.rs`:
- Line 29: Centralize the generated Handle identifier by adding a shared
handle_ident() helper in runtime/mod.rs, then replace the local
raw_ident("Handle") construction in the handle-generation code and the literal
Handle usage in entity_types with that helper. Ensure both generation sites
derive the type name from this single definition.

In `@crates/instrumentation/src/context.rs`:
- Around line 75-152: Replace Box<dyn std::error::Error> in
ContextInner::try_new and ContextInner::observer, plus Model::build_observers
and Context::try_new, with the crate’s typed thiserror-based error type. Add or
reuse variants that preserve runtime-resolution and exporter-construction
failures, and propagate those errors through the existing call paths without
changing successful behavior.

In `@crates/instrumentation/src/model.rs`:
- Around line 46-49: Add a concise comment directly above the fields in Context
explaining that observers must remain declared before inner so Rust drops
M::Observers and flushes ObserverInner instances before ContextInner tears down
its Runtime. Make the ordering requirement explicit, including that future
fields must not be inserted between them.
- Around line 19-43: Update the Model::build_observers return type to use the
same concrete, thread-safe error type required by ContextInner instead of
Box<dyn std::error::Error>, and adjust its documentation or callers as needed to
preserve error propagation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 7bcc3372-4d1a-47cf-b14c-ab292babee8a

📥 Commits

Reviewing files that changed from the base of the PR and between 42bb324 and dde7df7.

📒 Files selected for processing (20)
  • crates/codegen/src/cxx_bridge.rs
  • crates/codegen/src/pyo3_bridge.rs
  • crates/instrumentation-build/example/src/main.rs
  • crates/instrumentation-build/src/lib.rs
  • crates/instrumentation-build/src/runtime/context.rs
  • crates/instrumentation-build/src/runtime/handle.rs
  • crates/instrumentation-build/src/runtime/mod.rs
  • crates/instrumentation-build/src/runtime/observer.rs
  • crates/instrumentation/benches/event_emit.rs
  • crates/instrumentation/src/context.rs
  • crates/instrumentation/src/entity.rs
  • crates/instrumentation/src/handle.rs
  • crates/instrumentation/src/lib.rs
  • crates/instrumentation/src/model.rs
  • crates/instrumentation/src/observer.rs
  • crates/instrumentation/tests/collector_roundtrip.rs
  • crates/instrumentation/tests/runtime_flavors.rs
  • crates/model-macros/src/model_macro.rs
  • crates/model/src/lib.rs
  • integrations/nvtx/example/src/lib.rs
💤 Files with no reviewable changes (1)
  • crates/instrumentation-build/src/runtime/observer.rs

Comment thread crates/instrumentation-build/src/runtime/context.rs
Comment thread crates/instrumentation-build/src/runtime/mod.rs
Signed-off-by: Johan Peltenburg <johan.peltenburg+code@gmail.com>

@dhruv9vats dhruv9vats left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @johanpel, this is a nice generalization and feels more natural this way.

@joosthooz joosthooz left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't see the need for the coderabbit comments to be addressed, this looks good. One small comment is that there's now a quent_model::Observer and a quent_instrumentation::Observer which have different types, but one of them (?) is also referenced as #q::Observer so that could be confusing.

@johanpel

Copy link
Copy Markdown
Contributor Author

One small comment is that there's now a quent_model::Observer and a quent_instrumentation::Observer which have different types, but one of them (?) is also referenced as #q::Observer so that could be confusing.

quent_model_<X> crates are going to be removed soon so there may be a bit of overlap as we migrate, which is fine.

@johanpel

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit f4aafbd into rapidsai:main Jul 31, 2026
20 checks passed
rapids-bot Bot pushed a commit that referenced this pull request Jul 31, 2026
# Description

Stacked on #466.

Adds qualified schema type path support to `quent-instrumentation-build`. Schema namespaces become Rust modules instead of being flattened:

```rust
Foo::BarBaz → foo::BarBaz
FooBar::Baz → foo_bar::Baz
```

This preserves path boundaries and allows equal leaf names such as Foo::Query and Bar::Query to coexist.

Records, entities, events, references, handles, and observer storage follow the generated module hierarchy. Namespace-local AnyEvent enums compose into their parent namespace, while namespaces without events generate no aggregate.

## Related Issues

Follow-up to #449. Closes #442.

_Written by Codex._

Authors:
  - Johan Peltenburg (https://github.com/johanpel)

Approvers:
  - Dhruv Vats (https://github.com/dhruv9vats)

URL: #468
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants