Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

treewide: use ContextManagerMixin#592

Merged
bennyz merged 8 commits intojumpstarter-dev:mainfrom
NickCao:anyio-ctx-mixin
Sep 4, 2025
Merged

treewide: use ContextManagerMixin#592
bennyz merged 8 commits intojumpstarter-dev:mainfrom
NickCao:anyio-ctx-mixin

Conversation

@NickCao
Copy link
Copy Markdown
Collaborator

@NickCao NickCao commented Aug 20, 2025

Summary by CodeRabbit

  • Chores

    • Raised minimum AnyIO requirement to >=4.10.0 across project templates and multiple driver packages.
  • Refactor

    • Streamlined context-management across core components to use AnyIO mixins and generator-based context managers, simplifying resource lifecycle while preserving outward behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 20, 2025

Walkthrough

Raised the minimum anyio dependency to >=4.10.0 across templates and multiple driver packages. Converted several classes from AbstractContextManager/AbstractAsyncContextManager to AnyIO mixins and replaced explicit enter/exit/aenter/aexit implementations with generator-based contextmanager/asynccontextmanager methods.

Changes

Cohort / File(s) Summary
Template dependency bump
__templates__/driver/pyproject.toml.tmpl
Update anyio constraint: >=4.6.2.post1>=4.10.0.
Drivers dependency bump
packages/jumpstarter-driver-*/pyproject.toml
.../energenie, .../flashers, .../http-power, .../http, .../iscsi, .../probe-rs, .../shell, .../tasmota, .../tftp, .../yepkit
Update anyio constraint: >=4.6.2.post1>=4.10.0; manifest-only changes.
Network client context management
packages/jumpstarter-driver-network/jumpstarter_driver_network/client.py
Replace AbstractContextManager with ContextManagerMixin; remove __enter__/__exit__; add @contextmanager def __contextmanager__(...) yielding DbusAdapter; update imports/typing.
Lease context management
packages/jumpstarter/jumpstarter/client/lease.py
Replace ABC-based context managers with ContextManagerMixin and AsyncContextManagerMixin; remove self.manager wrapping; add @asynccontextmanager def __asynccontextmanager__ and @contextmanager def __contextmanager__; update typings/imports.
Session context management
packages/jumpstarter/jumpstarter/exporter/session.py
Replace AbstractContextManager with ContextManagerMixin; remove __enter__/__exit__; add @contextmanager def __contextmanager__ performing setup/teardown (logging handler, reset/close root device); update typings/imports.
Exporter context management
packages/jumpstarter/jumpstarter/exporter/exporter.py
Replace AbstractAsyncContextManager with AsyncContextManagerMixin; remove __aexit__; add @asynccontextmanager def __asynccontextmanager__ yielding self and performing async teardown/unregister via channel_factory with move_on_after/CancelScope; update typings/imports.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Lease as Lease (Async & Sync mixins)
  participant Resource as LeasedResource
  Note right of Lease: New generator-based async context manager
  User->>Lease: async with Lease(...)
  activate Lease
  Lease->>Resource: acquire / start
  Lease-->>User: yield Lease
  User->>Lease: exit
  Lease->>Resource: release / teardown
  deactivate Lease

  Note over Lease: Sync use bridged via __contextmanager__
  User->>Lease: with Lease(...)
  activate Lease
  Lease->>Resource: acquire (via portal.wrap_async_context_manager)
  Lease-->>User: yield Lease
  User->>Lease: exit
  Lease->>Resource: release
  deactivate Lease
Loading
sequenceDiagram
  autonumber
  actor Caller
  participant Client as DbusNetworkClient (ContextManagerMixin)
  participant Adapter as DbusAdapter
  Note right of Client: generator-based __contextmanager__ yields Adapter
  Caller->>Client: with DbusNetworkClient(...)
  activate Client
  Client->>Adapter: Adapter.__enter__()
  Client-->>Caller: yield Adapter
  Caller->>Client: exit
  Client->>Adapter: Adapter.__exit__()
  deactivate Client
Loading
sequenceDiagram
  autonumber
  actor Exporter
  participant Session as Session (ContextManagerMixin)
  participant Root as RootDevice
  participant Log as LoggingHandler
  Note right of Session: __contextmanager__ does setup -> yield -> teardown
  Exporter->>Session: with Session(...)
  activate Session
  Session->>Log: add handler
  Session->>Root: reset()
  Session-->>Exporter: yield Session
  Exporter->>Session: exit
  Session->>Root: close()
  Session->>Log: remove handler
  deactivate Session
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • mangelajo

Poem

A rabbit flips the context switch, hop!
Mixins scatter where enters used to stop.
AnyIO climbs to four-ten’s door,
Leases yield and exporters soar.
Carrots bumped — hop, code restored! 🥕🐇


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2abd8d3 and 9fa5a8d.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • __templates__/driver/pyproject.toml.tmpl (1 hunks)
  • packages/jumpstarter-driver-energenie/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-flashers/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-http-power/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-http/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-iscsi/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-network/jumpstarter_driver_network/client.py (2 hunks)
  • packages/jumpstarter-driver-probe-rs/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-shell/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-tasmota/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-tftp/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-yepkit/pyproject.toml (1 hunks)
  • packages/jumpstarter/jumpstarter/client/lease.py (3 hunks)
  • packages/jumpstarter/jumpstarter/exporter/exporter.py (3 hunks)
  • packages/jumpstarter/jumpstarter/exporter/session.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
  • packages/jumpstarter-driver-yepkit/pyproject.toml
  • packages/jumpstarter-driver-iscsi/pyproject.toml
  • templates/driver/pyproject.toml.tmpl
  • packages/jumpstarter-driver-shell/pyproject.toml
  • packages/jumpstarter-driver-tftp/pyproject.toml
  • packages/jumpstarter-driver-http-power/pyproject.toml
  • packages/jumpstarter-driver-flashers/pyproject.toml
  • packages/jumpstarter-driver-http/pyproject.toml
  • packages/jumpstarter/jumpstarter/client/lease.py
  • packages/jumpstarter-driver-energenie/pyproject.toml
  • packages/jumpstarter-driver-tasmota/pyproject.toml
  • packages/jumpstarter-driver-network/jumpstarter_driver_network/client.py
  • packages/jumpstarter-driver-probe-rs/pyproject.toml
🧰 Additional context used
🧬 Code graph analysis (2)
packages/jumpstarter/jumpstarter/exporter/session.py (1)
packages/jumpstarter/jumpstarter/driver/base.py (4)
  • Driver (56-269)
  • reset (89-91)
  • close (85-87)
  • report (192-209)
packages/jumpstarter/jumpstarter/exporter/exporter.py (4)
packages/jumpstarter/jumpstarter/config/client.py (1)
  • channel (111-120)
packages/jumpstarter/jumpstarter/config/exporter.py (1)
  • channel_factory (167-174)
packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/jumpstarter_pb2_grpc.py (1)
  • ControllerServiceStub (9-68)
packages/jumpstarter/conftest.py (1)
  • Unregister (59-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
🔇 Additional comments (2)
packages/jumpstarter/jumpstarter/exporter/exporter.py (2)

33-36: Good: channel_factory annotated as Awaitable and used accordingly

Accurate types; helps IDEs and static checks.


60-89: Guard against UnboundLocalError when channel_factory() times out

channel may be undefined if move_on_after cancels before assignment; closing it unconditionally will raise UnboundLocalError and mask the original issue.

     @asynccontextmanager
     async def __asynccontextmanager__(self) -> AsyncGenerator[Self]:
         try:
             yield self
         finally:
             try:
                 if self.registered:
                     logger.info("Unregistering exporter with controller")
                     try:
-                        with move_on_after(10):  # 10 second timeout
-                            channel = await self.channel_factory()
+                        channel = None
+                        with move_on_after(10):  # 10 second timeout
+                            channel = await self.channel_factory()
                             try:
                                 controller = jumpstarter_pb2_grpc.ControllerServiceStub(channel)
                                 await controller.Unregister(
                                     jumpstarter_pb2.UnregisterRequest(
                                         reason="Exporter shutdown",
                                     )
                                 )
                                 logger.info("Controller unregistration completed successfully")
                             finally:
-                                with CancelScope(shield=True):
-                                    await channel.close()
+                                if channel is not None:
+                                    with CancelScope(shield=True):
+                                        await channel.close()
                     except Exception as e:
                         logger.error("Error during controller unregistration: %s", e, exc_info=True)
 
             except Exception as e:
                 logger.error("Error during exporter cleanup: %s", e, exc_info=True)
                 # Don't re-raise to avoid masking the original exception

Likely an incorrect or invalid review comment.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@netlify
Copy link
Copy Markdown

netlify Bot commented Aug 20, 2025

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit 2c9258f
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/68b962d122bc7c0008102ce5
😎 Deploy Preview https://deploy-preview-592--jumpstarter-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@NickCao
Copy link
Copy Markdown
Collaborator Author

NickCao commented Aug 20, 2025

Make use of https://anyio.readthedocs.io/en/stable/contextmanagers.html, which has the added benefit of more robust error handling.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (3)
packages/jumpstarter-driver-http/pyproject.toml (1)

35-40: Remove duplicate pytest-asyncio entry; keep a single, modern version

There are two entries for pytest-asyncio (one with a placeholder 0.0.0). Keep one line (≥0.24.0 or newer) to match pytest 8.x, and drop the placeholder.

Rationale:

Apply:

 dev = [
     "pytest-cov>=6.0.0",
     "pytest>=8.3.3",
-    "pytest-asyncio>=0.0.0",
-    "pytest-asyncio>=0.24.0",
+    "pytest-asyncio>=0.24.0",
 ]

Optional: consider using AnyIO’s built-in pytest plugin instead of pytest-asyncio if you want cross-backend tests (no extra package needed). (anyio.readthedocs.io)

packages/jumpstarter-driver-tftp/pyproject.toml (1)

16-23: Pick a single async testing plugin; drop unnecessary pytest-anyio and placeholders

The dev group mixes pytest-anyio and pytest-asyncio, plus placeholder versions (0.0.0). Use one plugin consistently:

  • If your tests are asyncio-only, keep pytest-asyncio (≥0.24.0).
  • If you prefer AnyIO’s plugin, you don’t need pytest-anyio at all; it’s obsolete (the plugin ships with AnyIO).

References:

Example (keep pytest-asyncio):

 dev = [
     "pytest>=8.3.2",
     "pytest-cov>=6.0.0",
-    "pytest-anyio>=0.0.0",
-    "pytest-asyncio>=0.0.0",
+    "pytest-asyncio>=0.24.0",
     "jumpstarter-testing",
 ]

Alternative (use AnyIO’s plugin, no extra package):

 dev = [
     "pytest>=8.3.2",
     "pytest-cov>=6.0.0",
-    "pytest-anyio>=0.0.0",
-    "pytest-asyncio>=0.0.0",
     "jumpstarter-testing",
 ]
packages/jumpstarter-driver-probe-rs/pyproject.toml (1)

11-14: Invalid version specifier: click>=8.1.7.2 (no such release); fix to a valid version

Click uses standard three-part versions (e.g., 8.1.7, 8.1.8, 8.2.1). There is no 8.1.7.2 on PyPI; this pin will never resolve. Change to a valid version, e.g., >=8.1.7 (or consider >=8.2.1).

References: Click release history and latest versions on PyPI. (click.palletsprojects.com, pypi.org, data.safetycli.com)

Apply:

 dependencies = [
     "anyio>=4.10.0",
-    "click>=8.1.7.2",
+    "click>=8.1.7",
     "jumpstarter",
     "jumpstarter-driver-opendal",
 ]
🧹 Nitpick comments (1)
packages/jumpstarter-driver-shell/pyproject.toml (1)

9-9: Dependency bump to anyio>=4.10.0 — approved.

Minor nit: most other drivers list dependencies as a multi-line array; keep as-is if the repo accepts both styles, otherwise consider aligning for consistency in a follow-up.

-dependencies = ["anyio>=4.10.0", "jumpstarter"]
+dependencies = [
+  "anyio>=4.10.0",
+  "jumpstarter",
+]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 762e676 and 8db508a.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • __templates__/driver/pyproject.toml.tmpl (1 hunks)
  • packages/jumpstarter-driver-energenie/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-flashers/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-http-power/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-http/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-iscsi/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-network/jumpstarter_driver_network/client.py (2 hunks)
  • packages/jumpstarter-driver-probe-rs/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-shell/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-tasmota/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-tftp/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-yepkit/pyproject.toml (1 hunks)
  • packages/jumpstarter/jumpstarter/client/lease.py (3 hunks)
  • packages/jumpstarter/jumpstarter/exporter/session.py (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-01-29T11:52:43.554Z
Learnt from: bennyz
PR: jumpstarter-dev/jumpstarter#241
File: packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/client.py:52-60
Timestamp: 2025-01-29T11:52:43.554Z
Learning: The TFTP driver (packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/driver.py) handles all low-level concerns like path validation, error handling, and checksum computation. The client (packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/client.py) should remain simple as it delegates these responsibilities to the driver.

Applied to files:

  • packages/jumpstarter-driver-tftp/pyproject.toml
🧬 Code Graph Analysis (3)
packages/jumpstarter/jumpstarter/exporter/session.py (2)
packages/jumpstarter/jumpstarter/driver/base.py (2)
  • Driver (56-269)
  • reset (89-91)
packages/jumpstarter-driver-dutlink/jumpstarter_driver_dutlink/driver.py (2)
  • reset (120-121)
  • reset (183-184)
packages/jumpstarter-driver-network/jumpstarter_driver_network/client.py (2)
packages/jumpstarter-driver-network/jumpstarter_driver_network/adapters/dbus.py (1)
  • DbusAdapter (12-34)
packages/jumpstarter-driver-network/jumpstarter_driver_network/driver.py (2)
  • client (26-27)
  • client (160-161)
packages/jumpstarter/jumpstarter/client/lease.py (1)
packages/jumpstarter/jumpstarter/client/grpc.py (2)
  • Lease (112-192)
  • DeleteLease (383-389)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: e2e
🔇 Additional comments (11)
__templates__/driver/pyproject.toml.tmpl (1)

12-12: anyio>=4.10.0 is the correct floor for ContextManagerMixin usage — LGTM

The bump aligns with the new ContextManagerMixin/AsyncContextManagerMixin pattern introduced in AnyIO 4.10.0 and documented in AnyIO’s “Context manager mix-in classes.”
See: AnyIO 4.10.0 release notes and docs. (newreleases.io, anyio.readthedocs.io)

packages/jumpstarter-driver-http/pyproject.toml (1)

10-15: anyio floor bump is correct for ctx-mixin refactor — LGTM

The refactor to AnyIO’s ContextManagerMixin requires ≥4.10.0; this pin is appropriate.
Refs: AnyIO 4.10.0 release + docs on mixins. (newreleases.io, anyio.readthedocs.io)

packages/jumpstarter-driver-yepkit/pyproject.toml (1)

10-14: anyio>=4.10.0 bump matches new context-manager mixins — LGTM

No further changes needed here.
Refs for mixins: AnyIO 4.10.0 release + docs. (newreleases.io, anyio.readthedocs.io)

packages/jumpstarter-driver-tftp/pyproject.toml (1)

10-14: anyio floor bump appropriate — LGTM

Consistent with repo-wide migration to AnyIO’s context manager mixins.
Refs: AnyIO 4.10.0 release + docs. (newreleases.io, anyio.readthedocs.io)

packages/jumpstarter-driver-probe-rs/pyproject.toml (1)

10-14: anyio min-version update is correct for mixin-based context managers — LGTM

Aligned with AnyIO 4.10.0 features.
Refs: AnyIO 4.10.0 release + mixin docs. (newreleases.io, anyio.readthedocs.io)

packages/jumpstarter-driver-energenie/pyproject.toml (1)

12-12: AnyIO minimum raised to 4.10.0 — looks good.

Consistent with the repository-wide bump and the mixin-based context management.

packages/jumpstarter-driver-iscsi/pyproject.toml (1)

9-9: AnyIO ≥4.10.0 requirement — approved; no legacy pins found

– Searched for any *constraints*.txt or requirements*.txt files; none exist in the repository
– No older AnyIO versions are pinned via constraint or requirements files

packages/jumpstarter-driver-http-power/pyproject.toml (1)

12-12: Raise to anyio>=4.10.0 — approved.

Matches the rest of the drivers; no other changes needed here.

packages/jumpstarter/jumpstarter/exporter/session.py (1)

35-36: Adopting ContextManagerMixin: LGTM.

The class now cleanly derives enter/exit from the mixin. No issues spotted.

packages/jumpstarter/jumpstarter/client/lease.py (2)

30-30: Migration to ContextManagerMixin/AsyncContextManagerMixin: LGTM.

The shape matches AnyIO’s mixins and keeps sync/async parity.


137-141: Sync bridge via portal.wrap_async_context_manager: LGTM.

Yields Self and mirrors async behavior. No changes needed.

Comment thread __templates__/driver/pyproject.toml.tmpl
Comment thread packages/jumpstarter-driver-flashers/pyproject.toml
Comment thread packages/jumpstarter-driver-tasmota/pyproject.toml
Comment thread packages/jumpstarter/jumpstarter/client/lease.py
Comment thread packages/jumpstarter/jumpstarter/exporter/session.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/jumpstarter/jumpstarter/exporter/exporter.py (1)

33-44: Teardown may be skipped on exceptions; ensure unregister always runs and the gRPC channel is closed

With contextlib.asynccontextmanager, code after yield will not run if an exception propagates unless it’s in a finally block. Currently, an error inside the async with Exporter block will bypass Unregister. Also, the controller channel from channel_factory is never closed, which can leak connections if the factory returns a new channel each time (as suggested by the config snippet).

Refactor to:

  • Wrap cleanup in try/finally so Unregister runs even on error.
  • Use async with on the channel (assuming the factory returns an async context-manageable channel) to guarantee closure.
  • Don’t let teardown errors mask the original exception; log and continue.
  • Set registered back to False after attempting to unregister.
  • Fix the return type to AsyncGenerator[Self, None] to satisfy type checkers.
 @asynccontextmanager
-async def __asynccontextmanager__(self) -> AsyncGenerator[Self]:
-    yield self
-    if self.registered:
-        controller = jumpstarter_pb2_grpc.ControllerServiceStub(await self.channel_factory())
-        logger.info("Unregistering exporter with controller")
-        await controller.Unregister(
-            jumpstarter_pb2.UnregisterRequest(
-                reason="TODO",
-            )
-        )
+async def __asynccontextmanager__(self) -> AsyncGenerator[Self, None]:
+    try:
+        yield self
+    finally:
+        if self.registered:
+            logger.info("Unregistering exporter with controller")
+            try:
+                async with await self.channel_factory() as channel:
+                    controller = jumpstarter_pb2_grpc.ControllerServiceStub(channel)
+                    await controller.Unregister(
+                        jumpstarter_pb2.UnregisterRequest(
+                            reason="shutdown",
+                        )
+                    )
+            except Exception:
+                logger.warning(
+                    "Exporter teardown: Unregister failed; ignoring since we're exiting.",
+                    exc_info=True,
+                )
+            finally:
+                self.registered = False
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8db508a and a25fd40.

📒 Files selected for processing (1)
  • packages/jumpstarter/jumpstarter/exporter/exporter.py (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/jumpstarter/jumpstarter/exporter/exporter.py (1)
packages/jumpstarter/jumpstarter/config/exporter.py (1)
  • channel_factory (163-171)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: e2e
🔇 Additional comments (1)
packages/jumpstarter/jumpstarter/exporter/exporter.py (1)

25-31: Adoption of AsyncContextManagerMixin looks good; dataclass usage is consistent

Subclassing AsyncContextManagerMixin and providing the asynccontextmanager hook is aligned with AnyIO’s pattern. Field defaults and kw_only usage are consistent.

Comment thread packages/jumpstarter/jumpstarter/exporter/exporter.py Outdated
@mangelajo
Copy link
Copy Markdown
Member

oopps confict, can you rebase @NickCao ? sorry about that

@bennyz bennyz enabled auto-merge September 4, 2025 09:58
@bennyz bennyz merged commit 8d37657 into jumpstarter-dev:main Sep 4, 2025
18 checks passed
This was referenced Jan 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants