Skip to content

fix(import): wrap per-tag tag operation in SAVEPOINT to prevent Session poisoning - #42919

Open
waterWang wants to merge 1 commit into
apache:masterfrom
waterWang:fix/import-tag-savepoint
Open

fix(import): wrap per-tag tag operation in SAVEPOINT to prevent Session poisoning#42919
waterWang wants to merge 1 commit into
apache:masterfrom
waterWang:fix/import-tag-savepoint

Conversation

@waterWang

Copy link
Copy Markdown

Summary

Fixes #42912

import_tag catches SQLAlchemyError after a query-triggered autoflush failure and continues using the same SQLAlchemy Session without rolling back or isolating the failed per-tag operation in a SAVEPOINT.

When two concurrent tag imports race on the same TaggedObject association (uix_tagged_object unique constraint), one transaction commits first and the other receives a real UniqueViolation. import_tag catches it and continues, but the Session remains in SQLAlchemy's pending-rollback state, so the next Session operation raises PendingRollbackError.

The @transaction() decorator on import_tag is a no-op when called from ImportAssetsCommand.run() (which is already inside a transaction), so the poisoned session is not recovered before reuse.

Fix

Wrap each per-tag operation in a SAVEPOINT via db_session.begin_nested(). On failure the SAVEPOINT is rolled back, the failed tag is skipped, and the Session remains usable for subsequent tags — the concurrent import completes without discarding unrelated imports.

Why this approach

Testing

  • Test plan to be added (pending local Superset test environment)

@bito-code-review

bito-code-review Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #2711fd

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 59cc00f..59cc00f
    • superset/commands/importers/v1/utils.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Comment on lines +325 to +329
if tag is None:
description = tag_descriptions.get(tag_name, None)
tag = Tag(name=tag_name, description=description, type="custom")
db_session.add(tag)
existing_tags[tag_name] = tag # Update the existing_tags dict

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.

Suggestion: When two imports concurrently create the same tag, the losing insert raises a uniqueness error and rolls back this SAVEPOINT. The newly constructed Tag is then no longer persistent, but existing_tags still points to it, so this import skips the association instead of re-querying the tag row created by the winning transaction. Re-fetch the existing tag after the uniqueness conflict before continuing the association operation. [race condition]

Severity Level: Major ⚠️
- ❌ Concurrent imports can omit requested tag associations.
- ⚠️ Asset imports report success with incomplete tagging.
- ⚠️ The losing transaction does not reuse the winning tag row.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/commands/importers/v1/utils.py
**Line:** 325:329
**Comment:**
	*Race Condition: When two imports concurrently create the same tag, the losing insert raises a uniqueness error and rolls back this SAVEPOINT. The newly constructed `Tag` is then no longer persistent, but `existing_tags` still points to it, so this import skips the association instead of re-querying the tag row created by the winning transaction. Re-fetch the existing tag after the uniqueness conflict before continuing the association operation.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

db_session.add(new_tagged_object)

new_tag_ids.append(tag.id)
new_tag_ids.append(tag.id)

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.

Suggestion: new_tag_ids is updated before the nested transaction has successfully flushed and released its SAVEPOINT. If the pending association insert fails while leaving the context manager, the exception is caught but this ID remains in new_tag_ids, causing cleanup and the returned result to treat the failed tag as successfully imported. [logic error]

Severity Level: Minor 🧹
- ⚠️ `import_tag()` returns IDs for failed associations.
- ⚠️ Concurrent imports produce inaccurate tag results.
- ⚠️ Cleanup decisions use stale success state.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/commands/importers/v1/utils.py
**Line:** 343:343
**Comment:**
	*Logic Error: `new_tag_ids` is updated before the nested transaction has successfully flushed and released its SAVEPOINT. If the pending association insert fails while leaving the context manager, the exception is caught but this ID remains in `new_tag_ids`, causing cleanup and the returned result to treat the failed tag as successfully imported.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.41%. Comparing base (127a6f9) to head (59cc00f).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
superset/commands/importers/v1/utils.py 0.00% 13 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #42919      +/-   ##
==========================================
- Coverage   66.42%   66.41%   -0.01%     
==========================================
  Files        2857     2857              
  Lines      161293   161294       +1     
  Branches    37134    37134              
==========================================
- Hits       107133   107130       -3     
- Misses      52135    52138       +3     
- Partials     2025     2026       +1     
Flag Coverage Δ
hive 38.24% <0.00%> (-0.01%) ⬇️
mysql 57.78% <0.00%> (-0.01%) ⬇️
postgres 57.83% <0.00%> (-0.01%) ⬇️
presto 40.20% <0.00%> (-0.01%) ⬇️
python 59.22% <0.00%> (-0.01%) ⬇️
sqlite 57.45% <0.00%> (-0.01%) ⬇️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

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

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] import_tag reuses a poisoned SQLAlchemy Session after concurrent tag conflict

1 participant