Skip to content

fix(iorails): avoid IORails sync generate coroutine warning#1968

Open
Pouyanpi wants to merge 1 commit into
developfrom
pouyanpi/fix/iorails-sync-generate-warning
Open

fix(iorails): avoid IORails sync generate coroutine warning#1968
Pouyanpi wants to merge 1 commit into
developfrom
pouyanpi/fix/iorails-sync-generate-warning

Conversation

@Pouyanpi

@Pouyanpi Pouyanpi commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fix IORails.generate() so calling the synchronous API from inside an already-running event loop fails cleanly without leaking an unawaited coroutine warning.

Reproduction

On the current behavior, the focused test passes but emits a runtime warning:

poetry run pytest tests/guardrails/test_iorails.py::TestGenerate::test_generate_raises_when_called_from_async_loop -q -W always::RuntimeWarning

The warning appears as:

RuntimeWarning: coroutine 'IORails.generate.<locals>._run_sync_iorails' was never awaited

In a larger test run, it can surface from pytest internals, for example:

.../site-packages/_pytest/logging.py:351: RuntimeWarning: coroutine 'IORails.generate.<locals>._run_sync_iorails' was never awaited
  def __enter__(self) -> _HandlerType:
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

Fix

IORails.generate() now checks for a running event loop before constructing the internal coroutine. If called from async code, it raises the existing RuntimeError path cleanly and tells callers to use generate_async().

The test now also asserts that this path does not emit a RuntimeWarning.

Validation

poetry run pytest tests/guardrails/test_iorails.py::TestGenerate -q

Summary by CodeRabbit

  • Bug Fixes

    • Added runtime validation to prevent calling the synchronous generate method from within an async event loop, providing clear guidance to use the async alternative instead.
  • Tests

    • Enhanced tests to verify proper error handling and cleanup behavior.

@codecov

codecov Bot commented Jun 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Pouyanpi Pouyanpi changed the title fix: avoid IORails sync generate coroutine warning fix(iorails): avoid IORails sync generate coroutine warning Jun 2, 2026
@Pouyanpi Pouyanpi self-assigned this Jun 2, 2026
@Pouyanpi Pouyanpi added the bug Something isn't working label Jun 2, 2026
@Pouyanpi Pouyanpi added this to the v0.23.0 milestone Jun 2, 2026
@Pouyanpi Pouyanpi marked this pull request as ready for review June 2, 2026 08:45
@greptile-apps

greptile-apps Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a RuntimeWarning: coroutine '..._run_sync_iorails' was never awaited that was emitted when IORails.generate() was called from inside a running event loop. The root cause was that asyncio.run() would raise a RuntimeError after the coroutine object had already been created, leaving it unawaited.

  • iorails.py: Adds an early asyncio.get_running_loop() check before the _run_sync_iorails coroutine function is ever defined; if a loop is detected the method raises cleanly without constructing any coroutine object.
  • test_iorails.py: Strengthens the existing async-loop test to assert both the correct RuntimeError message and the absence of any RuntimeWarning, using gc.collect() to flush any lingering coroutine objects before the assertion.

Confidence Score: 4/5

Safe to merge; the change is a targeted early-exit guard with a matching test upgrade.

The fix is correct and the test robustly covers the fixed path. The only rough edge is that the deep copy of config still executes before the loop guard, wasting a small amount of work on the error path — not a correctness issue, just ordering that could be tightened.

nemoguardrails/guardrails/iorails.py — the loop check could be moved above the model_copy call.

Important Files Changed

Filename Overview
nemoguardrails/guardrails/iorails.py Adds an early running-loop guard to generate(); the check is placed after an unnecessary deep copy of config that runs even on the error path.
tests/guardrails/test_iorails.py Test upgraded to assert no RuntimeWarning is emitted and to match the new error message; gc.collect() ensures any zombie coroutines are flushed before the assertion.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["IORails.generate(messages)"] --> B["model_copy(deep=True)\n+ disable tracing/metrics"]
    B --> C{"asyncio.get_running_loop()"}
    C -- "RuntimeError\n(no loop running)" --> D["define _run_sync_iorails()"]
    D --> E["asyncio.run(_run_sync_iorails())"]
    E --> F["IORails engine\ngenerate_async()"]
    F --> G["return LLMMessage"]
    C -- "loop found\n(no exception)" --> H["raise RuntimeError\n'Use generate_async() instead'"]
Loading

Comments Outside Diff (1)

  1. nemoguardrails/guardrails/iorails.py, line 265-279 (link)

    P2 The deep copy of sync_config (and the two attribute mutations that follow) runs unconditionally before the running-loop check. When generate() is called from an async context the copy is allocated and then immediately discarded on the raise. Moving the guard to the top of the method avoids unnecessary work on the error path.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: nemoguardrails/guardrails/iorails.py
    Line: 265-279
    
    Comment:
    The deep copy of `sync_config` (and the two attribute mutations that follow) runs unconditionally before the running-loop check. When `generate()` is called from an async context the copy is allocated and then immediately discarded on the `raise`. Moving the guard to the top of the method avoids unnecessary work on the error path.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
nemoguardrails/guardrails/iorails.py:265-279
The deep copy of `sync_config` (and the two attribute mutations that follow) runs unconditionally before the running-loop check. When `generate()` is called from an async context the copy is allocated and then immediately discarded on the `raise`. Moving the guard to the top of the method avoids unnecessary work on the error path.

```suggestion
        try:
            asyncio.get_running_loop()
        except RuntimeError:
            pass
        else:
            raise RuntimeError(
                "IORails.generate() cannot be called from a running event loop. Use generate_async() instead."
            )

        # Disable tracing and metrics for synchronous generation calls
        sync_config = self.config.model_copy(deep=True)
        if sync_config.tracing is not None:
            sync_config.tracing.enabled = False
        if sync_config.metrics is not None:
            sync_config.metrics.enabled = False
```

Reviews (1): Last reviewed commit: "fix: avoid IORails sync generate corouti..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f201a995-1942-44b3-a2eb-cd1c080adf02

📥 Commits

Reviewing files that changed from the base of the PR and between 8082e74 and 88f25c8.

📒 Files selected for processing (2)
  • nemoguardrails/guardrails/iorails.py
  • tests/guardrails/test_iorails.py

📝 Walkthrough

Walkthrough

IORails.generate() adds an early event-loop detection guard that raises RuntimeError when called from within a running asyncio event loop. The implementation and test validate this guard prevents asyncio.run() misuse while ensuring clean teardown without runtime warnings.

Changes

Event-loop guard in generate()

Layer / File(s) Summary
Event-loop guard implementation
nemoguardrails/guardrails/iorails.py
IORails.generate() checks asyncio.get_running_loop() and raises RuntimeError instructing callers to use generate_async() instead when invoked from a running event loop.
Guard validation and warning cleanup
tests/guardrails/test_iorails.py
Test imports gc and warnings to capture teardown state. test_generate_raises_when_called_from_async_loop wraps the event-loop violation scenario with warning capture, forces garbage collection, and asserts no RuntimeWarning occurred.

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(iorails): avoid IORails sync generate coroutine warning' directly and accurately summarizes the main change: preventing a RuntimeWarning from being emitted when IORails.generate() is called from an async context.
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.
Test Results For Major Changes ✅ Passed Minor bug fix (9 lines added) with test coverage; no major changes, no numerics/performance impacts, and test updated to verify new behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pouyanpi/fix/iorails-sync-generate-warning

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

@Pouyanpi Pouyanpi requested a review from tgasser-nv June 2, 2026 09:10

@tgasser-nv tgasser-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM! Just a tiny nit to move the check before config copying and mutaton

if sync_config.metrics is not None:
sync_config.metrics.enabled = False

try:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: Could you move this up to start at line 265 so we don't mutate any of the tracing config if we're going to raise an error?

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants