Skip to content

fix(tf): close EwaldRecp TensorFlow sessions#5708

Merged
njzjz merged 3 commits into
deepmodeling:masterfrom
wanghan-iapcm:fix-tf-ewald-session-close
Jul 7, 2026
Merged

fix(tf): close EwaldRecp TensorFlow sessions#5708
njzjz merged 3 commits into
deepmodeling:masterfrom
wanghan-iapcm:fix-tf-ewald-session-close

Conversation

@wanghan-iapcm

@wanghan-iapcm wanghan-iapcm commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Problem

EwaldRecp creates a dedicated tf.Session in its constructor but exposes no way to close it (no close(), context-manager path, or destructor). DipoleChargeModifier creates and keeps an EwaldRecp instance without releasing it either. Creating and discarding these objects therefore leaks TensorFlow sessions and graph resources; long-running processes that repeatedly construct modifiers accumulate them until process exit.

Fix

Add close() (idempotent, guarded), context-manager support (__enter__/__exit__), and a defensive __del__ to EwaldRecp, and have DipoleChargeModifier.close() forward to the held EwaldRecp.

Test

test_ewald.py previously exercised only eval, never lifecycle. New tests assert that EwaldRecp.close() and context-manager exit release the session (a subsequent eval raises on the closed session), and that DipoleChargeModifier.close() forwards to its EwaldRecp.close(). All three fail on the current code with AttributeError (no such methods) and pass after the fix.

Fix #5685

Summary by CodeRabbit

  • New Features
    • Added explicit resource cleanup and context-manager support for TensorFlow evaluators, including Ewald reciprocal, TensorFlow DeepEval backends, and the DeepEval wrapper.
    • Added close() forwarding for the dipole-charge modifier to release its associated evaluator resources.
  • Tests
    • Added unit tests validating close() behavior, context-manager exit, and error handling after sessions are released for Ewald reciprocal and DeepEval (including wrapper forwarding and no-op behavior when nothing is cached).
    • Added a test ensuring dipole-charge modifier cleanup forwards correctly.

EwaldRecp creates a dedicated tf.Session in its constructor but exposed no way
to close it, and DipoleChargeModifier holds an EwaldRecp without releasing it.
Repeatedly constructing and discarding these objects leaked TensorFlow sessions
and graph resources in long-running processes.

Add close(), context-manager support, and a defensive __del__ to EwaldRecp, and
have DipoleChargeModifier.close() forward to the EwaldRecp. Add tests that the
session is released after close()/context-manager exit and that the modifier
forwards close to its evaluator.

Fix deepmodeling#5685
@wanghan-iapcm wanghan-iapcm requested a review from njzjz July 2, 2026 01:32
@dosubot dosubot Bot added the bug label Jul 2, 2026
@github-actions github-actions Bot added the Python label Jul 2, 2026
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds session cleanup and context-manager support across TensorFlow evaluator classes, forwards cleanup through DipoleChargeModifier, and adds tests covering close behavior and exit-time cleanup.

Changes

TensorFlow session lifecycle

Layer / File(s) Summary
DeepEval backend and wrapper close
deepmd/infer/deep_eval.py
Adds backend and wrapper close() methods plus context-manager entry and exit methods.
TensorFlow evaluator cleanup
deepmd/tf/infer/deep_eval.py
Updates Self imports and adds cached-session-safe close(), context-manager, and __del__ cleanup to DeepEval and DeepEvalOld.
EwaldRecp and modifier forwarding
deepmd/tf/infer/ewald_recp.py, deepmd/tf/modifier/dipole_charge.py
Adds EwaldRecp session closing and context-manager methods, and forwards close() from DipoleChargeModifier to its Ewald reciprocal evaluator.
Cleanup tests
source/tests/tf/test_dipolecharge.py, source/tests/tf/test_ewald.py, source/tests/tf/test_deepeval_close.py
Adds tests for close forwarding, evaluator session cleanup, context-manager exit, wrapper delegation, and the base backend no-op close.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DeepEval
  participant DeepEvalBackend
  participant TFSession

  DeepEval->>DeepEval: close()
  DeepEval->>DeepEvalBackend: close()
  DeepEvalBackend->>TFSession: release resources if any
  DeepEval->>DeepEval: __exit__()
  DeepEval->>DeepEvalBackend: close()
Loading

Related Issues: #5685

Suggested labels: enhancement, tests

Suggested reviewers: njzjz

🚥 Pre-merge checks | ✅ 5
✅ 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 accurately reflects the main change: adding TensorFlow session cleanup for EwaldRecp.
Linked Issues check ✅ Passed The PR adds EwaldRecp.close(), forwards closure from DipoleChargeModifier, and includes context-manager and destructor support with tests.
Out of Scope Changes check ✅ Passed The DeepEval lifecycle updates are still directly related to the same TensorFlow session leak cleanup and are not unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@deepmd/tf/modifier/dipole_charge.py`:
- Around line 108-113: The DipoleChargeModifier.close method only releases the
Ewald evaluator stored in self.er and leaves the TensorFlow session inherited
from DeepDipoleOld open. Update close() in DipoleChargeModifier to also guard
and close self.sess after closing er, so repeated create/discard cycles fully
release both sessions.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 91599b1b-6ed5-4ae4-bbea-09f84b087cc3

📥 Commits

Reviewing files that changed from the base of the PR and between 0f19772 and 6bc3a4d.

📒 Files selected for processing (4)
  • deepmd/tf/infer/ewald_recp.py
  • deepmd/tf/modifier/dipole_charge.py
  • source/tests/tf/test_dipolecharge.py
  • source/tests/tf/test_ewald.py

Comment thread deepmd/tf/modifier/dipole_charge.py
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.66667% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.48%. Comparing base (0e5c170) to head (7901e91).
⚠️ Report is 34 commits behind head on master.

Files with missing lines Patch % Lines
deepmd/tf/infer/deep_eval.py 80.00% 4 Missing ⚠️
deepmd/tf/infer/ewald_recp.py 85.71% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5708      +/-   ##
==========================================
- Coverage   81.97%   79.48%   -2.49%     
==========================================
  Files         959     1014      +55     
  Lines      105748   115412    +9664     
  Branches     4102     4275     +173     
==========================================
+ Hits        86684    91739    +5055     
- Misses      17573    22130    +4557     
- Partials     1491     1543      +52     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@njzjz njzjz 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.

It seems to me that all TensorFlow DeepEval has a sess attribute. So I am not sure if it is useful to fix only one class.

Generalizes the EwaldRecp session-close fix to every TensorFlow DeepEval evaluator, which each cache a tf.Session via the sess cached_property and previously leaked it. Adds a no-op close() plus context-manager support on the DeepEvalBackend base, overrides close() on the TF DeepEval and legacy DeepEvalOld backends to close and drop the cached session (guarded so it is never materialized just to be closed), and forwards close()/__enter__/__exit__ from the high-level DeepEval wrapper. Addresses the review note that the leak affects all TF DeepEval classes, not only EwaldRecp.
@wanghan-iapcm

Copy link
Copy Markdown
Collaborator Author

Good point — addressed in f54d49a. You are right that every TF DeepEval caches a tf.Session via the sess cached_property and had the same leak, so I generalized the fix rather than touching only EwaldRecp: DeepEvalBackend now provides a no-op close() plus context-manager support, the TF DeepEval and legacy DeepEvalOld backends override close() to close and drop the cached session (guarded so it is never materialized just to be closed, and the cache is cleared so a later access can recreate it), and the high-level DeepEval wrapper forwards close()/__enter__/__exit__. Added source/tests/tf/test_deepeval_close.py covering both branches (session materialized vs not), the context manager, and the wrapper forwarding. Happy to rename the PR title/body to reflect the broader scope if you prefer.

Comment thread source/tests/tf/test_deepeval_close.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
deepmd/tf/infer/deep_eval.py (1)

1247-1263: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider guarding __del__ against exceptions during interpreter shutdown.

__del__ unconditionally calls close(), which calls sess.close(). If module-level TF/Python state is already torn down at interpreter shutdown, this can raise noisy "Exception ignored in..." messages rather than propagating cleanly. Wrapping the shutdown path in a try/except Exception: pass is a common mitigation for __del__ cleanup methods, distinct from the explicit close() call which should still raise normally.

🛡️ Optional guard for `__del__`
     def __del__(self) -> None:
-        self.close()
+        try:
+            self.close()
+        except Exception:
+            pass
🤖 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 `@deepmd/tf/infer/deep_eval.py` around lines 1247 - 1263, Guard the cleanup
path in the DeepEval destructor: keep `close()` unchanged so explicit calls
still surface errors, but update `__del__` in `deep_eval.py` to wrap
`self.close()` in a broad `try/except Exception` and ignore failures that can
occur during interpreter shutdown when TensorFlow/Python state may already be
torn down. Use the existing `close`, `__enter__`, `__exit__`, and `__del__`
methods to locate the change.
🤖 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 `@deepmd/infer/deep_eval.py`:
- Around line 96-108: DeepEvalBackend.close is intentionally a no-op default,
but Ruff B027 will flag the empty method and fail checks. Update the close
method in DeepEvalBackend to explicitly suppress the no-op warning (for example
with a targeted lint ignore or equivalent) while keeping __enter__ and __exit__
behavior unchanged. Make sure the suppression is scoped to this default
implementation so subclasses can still override close normally.

---

Nitpick comments:
In `@deepmd/tf/infer/deep_eval.py`:
- Around line 1247-1263: Guard the cleanup path in the DeepEval destructor: keep
`close()` unchanged so explicit calls still surface errors, but update `__del__`
in `deep_eval.py` to wrap `self.close()` in a broad `try/except Exception` and
ignore failures that can occur during interpreter shutdown when
TensorFlow/Python state may already be torn down. Use the existing `close`,
`__enter__`, `__exit__`, and `__del__` methods to locate the change.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4cba7939-241f-43c0-9a9b-a6db31e66a5d

📥 Commits

Reviewing files that changed from the base of the PR and between 6bc3a4d and f54d49a.

📒 Files selected for processing (3)
  • deepmd/infer/deep_eval.py
  • deepmd/tf/infer/deep_eval.py
  • source/tests/tf/test_deepeval_close.py

Comment thread deepmd/infer/deep_eval.py
@wanghan-iapcm wanghan-iapcm requested a review from njzjz July 7, 2026 07:25
Wrap the session cleanup in the EwaldRecp/DeepEval/DeepEvalOld __del__ methods in try/except so garbage collection during interpreter shutdown, when TF/Python state may already be torn down, does not emit noisy "Exception ignored in" messages; the explicit close() path still raises normally. Also consolidate the duplicate unittest import in the new test into a single from-import to satisfy the CodeQL py/import-and-import-from warning.
@wanghan-iapcm

Copy link
Copy Markdown
Collaborator Author

Pushed 7901e91 with the review follow-ups:

  • __del__ shutdown guard (CodeRabbit nitpick): wrapped self.close() in try/except in the EwaldRecp/DeepEval/DeepEvalOld destructors so GC during interpreter shutdown (when TF/Python state may already be torn down) does not emit noisy "Exception ignored in" messages; the explicit close() path still raises normally.
  • CodeQL py/import-and-import-from: consolidated the duplicate unittest import in the new test into a single from unittest import ....
  • B027 (empty base close()): skipped — ruff check . passes locally and pre-commit.ci is green, so B027 is not enabled in the repo ruff config; a # noqa would be an unused suppression (replied inline).

The earlier Test Python (4, 3.10) failure was not a test failure — the job died during dependency install with a transient Failed to read --find-links URL: .../paddlepaddle/nightly protocol error from the PaddlePaddle mirror. This push re-triggered CI.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
deepmd/tf/infer/deep_eval.py (1)

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

Duplicate close()/__del__ logic vs. DeepEval (lines 312-327).

The body of close() and __del__ in DeepEvalOld is byte-for-byte identical to the version in DeepEval above. Since DeepEvalOld doesn't share a base class with DeepEval here, consider extracting a small mixin (e.g., _CachedSessionCloseMixin) with close()/__del__ to avoid maintaining two copies of the same cleanup logic.

♻️ Suggested mixin extraction
class _CachedSessionCloseMixin:
    """Shared close()/__del__ for classes with a cached_property `sess`."""

    def close(self) -> None:
        sess = self.__dict__.pop("sess", None)
        if sess is not None:
            sess.close()

    def __del__(self) -> None:
        try:
            self.close()
        except Exception:
            pass
🤖 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 `@deepmd/tf/infer/deep_eval.py` around lines 1252 - 1273, The cleanup logic in
DeepEvalOld duplicates the same close()/__del__ behavior already used by
DeepEval, so extract that shared cached-session teardown into a small mixin such
as _CachedSessionCloseMixin and have both DeepEval and DeepEvalOld inherit from
it. Move the session cache pop-and-close logic from close() plus the guarded
__del__ shutdown handling into the shared mixin, then remove the duplicated
implementations from DeepEvalOld so the two classes use one maintained copy of
the cleanup code.
🤖 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.

Nitpick comments:
In `@deepmd/tf/infer/deep_eval.py`:
- Around line 1252-1273: The cleanup logic in DeepEvalOld duplicates the same
close()/__del__ behavior already used by DeepEval, so extract that shared
cached-session teardown into a small mixin such as _CachedSessionCloseMixin and
have both DeepEval and DeepEvalOld inherit from it. Move the session cache
pop-and-close logic from close() plus the guarded __del__ shutdown handling into
the shared mixin, then remove the duplicated implementations from DeepEvalOld so
the two classes use one maintained copy of the cleanup code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 82778837-297a-4353-954f-c36c2f6b1fd5

📥 Commits

Reviewing files that changed from the base of the PR and between f54d49a and 7901e91.

📒 Files selected for processing (3)
  • deepmd/tf/infer/deep_eval.py
  • deepmd/tf/infer/ewald_recp.py
  • source/tests/tf/test_deepeval_close.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/tests/tf/test_deepeval_close.py

Comment thread deepmd/tf/infer/deep_eval.py Dismissed
Comment thread deepmd/tf/infer/ewald_recp.py Dismissed
@njzjz njzjz added this pull request to the merge queue Jul 7, 2026
Merged via the queue into deepmodeling:master with commit aff66bd Jul 7, 2026
56 of 57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Code scan] Close TensorFlow EwaldRecp sessions

3 participants