Skip to content

fix(dpmodel): reject incompatible LMDB merge type maps - #5838

Merged
njzjz merged 3 commits into
deepmodeling:masterfrom
njzjz-bot:fix/lmdb-merge-type-map-5634
Aug 2, 2026
Merged

fix(dpmodel): reject incompatible LMDB merge type maps#5838
njzjz merged 3 commits into
deepmodeling:masterfrom
njzjz-bot:fix/lmdb-merge-type-map-5634

Conversation

@njzjz-bot

@njzjz-bot njzjz-bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Fixes #5634

Summary

  • preflight every source LMDB type map before replacing or creating the destination
  • preserve byte-for-byte merging for sources with identical explicit maps
  • preserve legacy behavior when every source omits type-map metadata
  • reject reordered maps and explicit/missing mixtures because raw atom-type indices cannot be proven compatible
  • include every source path and map in validation errors

This chooses fail-fast validation instead of decoding and rewriting frames. A remapping implementation would also need to keep atom_types, atom_names, atom_numbs, and system_info mutually consistent; rejecting incompatible metadata is the smaller safe change.

Why existing tests missed this

The merge coverage used either two mapless legacy databases or sources with the same ["O", "H"] map. Its type-map test inspected only the merged metadata and never read frames from both source segments. Because every fixture generated the same numeric 0/1 type ordering, raw frame copying preserved all tested shapes, counts, and IDs even though a reversed source map would change their species meaning.

The regular LMDB remapping tests cover one database mapped to a model type map, not multiple source maps being collapsed into one merged metadata map. Reader/dataset consistency checks would also interpret the same corrupted merged metadata and therefore agree with each other.

Validation

  • source/tests/pt/test_lmdb_dataloader.py: 53 passed
  • ruff format .
  • ruff check .

Coding agent: Codex
Codex version: codex-cli 0.144.4
Model: gpt-5.6-sol
Reasoning effort: xhigh

Summary by CodeRabbit

  • Bug Fixes
    • Improved LMDB merging validation to prevent combining datasets with missing, incompatible, or reordered atom-type mappings.
    • Prevented creation or modification of the destination dataset when validation fails.
    • Preserved atom-type mappings and indices correctly in successfully merged datasets.

Preflight all source metadata before touching the destination. Permit identical explicit maps and all-mapless legacy inputs, but reject reordered maps and explicit/missing mixtures that byte-for-byte frame copying cannot preserve safely.

Cover incompatible and ambiguous sources, destination preservation on validation failure, and frame types from each segment of a valid merge.

Coding-Agent: Codex
Codex-Version: codex-cli 0.144.4
Model: gpt-5.6-sol
Reasoning-Effort: xhigh
@dosubot dosubot Bot added the bug label Jul 16, 2026
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

merge_lmdb now validates type_map metadata before modifying the destination. It preserves compatible mappings, releases source environments, and conditionally writes merged metadata. Tests cover atom-type indices, incompatible maps, mixed legacy metadata, and destination safety.

LMDB type-map-safe merging

Layer / File(s) Summary
Validate metadata before merging
deepmd/dpmodel/utils/lmdb_data.py
merge_lmdb preloads and validates source metadata before destination changes. It copies frames using the validated metadata and writes a shared type_map when available.
Verify merge mappings and failure safety
source/tests/pt/test_lmdb_dataloader.py
Tests verify atom-type indices and ensure incompatible or mixed metadata leaves the destination unchanged or absent.

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

Sequence Diagram(s)

sequenceDiagram
  participant merge_lmdb
  participant SourceLMDBs
  participant TypeMapValidator
  participant DestinationLMDB
  merge_lmdb->>SourceLMDBs: Read metadata from all sources
  merge_lmdb->>TypeMapValidator: Validate type_map compatibility
  TypeMapValidator-->>merge_lmdb: Return shared mapping or ValueError
  merge_lmdb->>DestinationLMDB: Create or replace destination after validation
  merge_lmdb->>SourceLMDBs: Copy frames and release environments
  merge_lmdb->>DestinationLMDB: Write validated metadata
Loading

Possibly related PRs

Suggested reviewers: wanghan-iapcm

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: rejecting incompatible LMDB type maps during merging.
Linked Issues check ✅ Passed The PR addresses issue #5634 by validating source type maps, rejecting incompatible or ambiguous combinations, and adding regression tests.
Out of Scope Changes check ✅ Passed The changes remain within the linked issue scope and cover LMDB merge validation, destination safety, and related regression tests.
✨ 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.

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.22%. Comparing base (cc689a7) to head (d2c4243).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5838      +/-   ##
==========================================
- Coverage   79.47%   79.22%   -0.25%     
==========================================
  Files        1072     1072              
  Lines      125041   125056      +15     
  Branches     4536     4541       +5     
==========================================
- Hits        99373    99075     -298     
- Misses      24044    24356     +312     
- Partials     1624     1625       +1     

☔ 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 commented Jul 18, 2026

Copy link
Copy Markdown
Member

Possible reviewers based on changed lines, exact file history, and exact-file review history:

  • @wanghan-iapcm — 1 commits on changed files; 4 reviews on exact changed files (deepmd/dpmodel/utils/lmdb_data.py, source/tests/pt/test_lmdb_dataloader.py).

No review request was made automatically.

Coding agent: Codex
Codex version: codex-cli 0.144.4
Model: gpt-5.6-sol
Reasoning effort: xhigh

@njzjz
njzjz requested review from wanghan-iapcm and removed request for wanghan-iapcm July 18, 2026 07:16

@wanghan-iapcm wanghan-iapcm 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.

The fix is correct and the fail-fast-before-touching-the-destination ordering is well tested (marker-preserved / dst-not-created). One test-coverage suggestion inline. Also flagging a coordination point: this PR and the open #5797 both rewrite merge_lmdb's env teardown and change the same src_env.close() -> _close_lmdb(src_path) line, so they will conflict — worth deciding a merge order (this PR already fixes #5797's shared-reader .close() bug; #5797 additionally wraps the frame-copy loop in try/finally for #5635).

Comment thread source/tests/pt/test_lmdb_dataloader.py Outdated
Coding-Agent: Codex
Codex-Version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning-Effort: xhigh

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

Caution

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

⚠️ Outside diff range comments (1)
deepmd/dpmodel/utils/lmdb_data.py (1)

1822-1887: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Merge loop and dst_env aren't exception-safe; a mid-loop failure leaks the cached source env and leaves dst_env unclosed.

Unlike the pre-load loop (which wraps _close_lmdb in try/finally), this loop calls _close_lmdb(src_path) at Line 1873 only after the with block finishes normally. If anything inside the loop raises (e.g. msgpack.unpackb on a malformed raw value, or an unexpected metadata shape), the ref-counted cached env for that source is never released, and dst_env (opened at Line 1814) is never closed either — both leak on the error path.

🔒️ Proposed fix
     for src_path, meta in source_metadata:
         src_env = _open_lmdb(src_path)
-        nframes, src_fmt, natoms_per_type = _parse_metadata(meta)
-        fallback_natoms = sum(natoms_per_type)
-        ...
-        with src_env.begin() as src_txn, dst_env.begin(write=True) as dst_txn:
-            ...
-        ...
-        _close_lmdb(src_path)
+        try:
+            nframes, src_fmt, natoms_per_type = _parse_metadata(meta)
+            fallback_natoms = sum(natoms_per_type)
+            ...
+            with src_env.begin() as src_txn, dst_env.begin(write=True) as dst_txn:
+                ...
+            ...
+        finally:
+            _close_lmdb(src_path)

Similarly wrap the body from dst_env = lmdb.open(...) through the metadata write in try/finally: dst_env.close().

🤖 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/dpmodel/utils/lmdb_data.py` around lines 1822 - 1887, Make the merge
operation exception-safe by wrapping the full destination workflow—from the
`dst_env` open through the merged metadata write—in `try/finally` and always
closing `dst_env`. Within the `for src_path, meta in source_metadata` loop,
ensure each source environment opened by `_open_lmdb` is released with
`_close_lmdb(src_path)` in a per-source `finally` block so failures during
transaction processing or metadata parsing cannot leak either environment.
🤖 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.

Outside diff comments:
In `@deepmd/dpmodel/utils/lmdb_data.py`:
- Around line 1822-1887: Make the merge operation exception-safe by wrapping the
full destination workflow—from the `dst_env` open through the merged metadata
write—in `try/finally` and always closing `dst_env`. Within the `for src_path,
meta in source_metadata` loop, ensure each source environment opened by
`_open_lmdb` is released with `_close_lmdb(src_path)` in a per-source `finally`
block so failures during transaction processing or metadata parsing cannot leak
either environment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ef126776-0db4-45d5-be26-d0173ad8e5ef

📥 Commits

Reviewing files that changed from the base of the PR and between 6c3b985 and 6100595.

📒 Files selected for processing (2)
  • deepmd/dpmodel/utils/lmdb_data.py
  • source/tests/pt/test_lmdb_dataloader.py

@njzjz
njzjz requested a review from wanghan-iapcm August 1, 2026 13:38
Resolve the LMDB merge conflict by combining fail-fast type-map validation with master’s exception-safe environment leasing. Reuse preflight metadata during copying to avoid a second metadata decode.

Coding-Agent: Codex
Codex-Version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning-Effort: xhigh

@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/dpmodel/utils/lmdb_data.py (1)

2847-2879: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider removing the partial destination when a merge fails.

The finally block closes dst_env, but the partially written LMDB directory stays at dst_path. A later reader can open it and read an incomplete dataset without metadata, or with stale metadata from a previous run. The validation preflight already protects an existing destination; this gap only affects the newly created output.

♻️ Proposed cleanup on failure
     dst_env = lmdb.open(dst_path, map_size=map_size)
     frame_idx = 0
     fmt = "012d"
     frame_nlocs: list[int] = []
     frame_system_ids: list[int] = []
     first_system_info: dict | None = None
     sys_id_offset = 0
+    merge_succeeded = False
     try:
         for src_path, metadata in source_metadata:
@@
         with dst_env.begin(write=True) as transaction:
             transaction.put(
                 b"__metadata__",
                 msgpack.packb(merged_meta, use_bin_type=True),
             )
+        merge_succeeded = True
     finally:
         dst_env.close()
+        if not merge_succeeded:
+            shutil.rmtree(dst_path, ignore_errors=True)

Note: source/tests/pt/test_lmdb_dataloader.py::test_failed_merge_releases_source_lease asserts only the source lease refcount, so this change stays compatible with it.

🤖 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/dpmodel/utils/lmdb_data.py` around lines 2847 - 2879, Update the merge
failure handling around the destination creation and the existing try/finally
that closes dst_env so any newly created destination at dst_path is removed when
the merge fails. Preserve existing destinations protected by validation
preflight, and ensure cleanup occurs only after an unsuccessful merge without
affecting source lease release or successful output.
🤖 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/dpmodel/utils/lmdb_data.py`:
- Around line 2847-2879: Update the merge failure handling around the
destination creation and the existing try/finally that closes dst_env so any
newly created destination at dst_path is removed when the merge fails. Preserve
existing destinations protected by validation preflight, and ensure cleanup
occurs only after an unsuccessful merge without affecting source lease release
or successful output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7133280b-c11c-4a67-828f-e5642356d989

📥 Commits

Reviewing files that changed from the base of the PR and between 6100595 and d2c4243.

📒 Files selected for processing (1)
  • deepmd/dpmodel/utils/lmdb_data.py

@wanghan-iapcm wanghan-iapcm 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.

Addressed exactly as asked, including the part I cared about most.

The parametrization uses ["O", "H", "N"] against a base of ["O", "H"], which is genuinely the prefix-compatible case -- indices 0 and 1 denote the same species in both maps, so a byte-for-byte copy would in fact be safe there, and the PR rejects it anyway. That is the boundary worth pinning, and the ids=["reordered", "prefix-compatible-superset"] labels make it obvious at a glance which case is which when one of them fails. The comment records the reasoning in the right terms too: requiring identical metadata rather than proving frame-by-frame safety is a deliberate conservative choice, and now a future attempt to relax it has to argue with a red test instead of slipping through.

I ran the class against a clean master baseline rather than reading it. Three fail unpatched -- both parametrizations plus test_merge_rejects_mixed_explicit_and_missing_type_maps -- and all eight pass after. Worth noting the tests also assert the rejection happens before any output is written (marker.read_text() == "preserve me" and not dst.exists()), which is what makes this a data-safety fix rather than just a nicer error message; a version that validated after opening the destination would still pass a naive pytest.raises check but fail these.

Approving.

@njzjz
njzjz added this pull request to the merge queue Aug 2, 2026
Merged via the queue into deepmodeling:master with commit 733f94a Aug 2, 2026
58 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] Reject or remap incompatible type maps when merging LMDB datasets

3 participants