Skip to content

fix(permalink): use a shared lock when re-reading a concurrently created permalink - #45102

Merged
michael-s-molina merged 3 commits into
apache:masterfrom
luizotavio32:fix/permalink-shared-lock-deadlock-master
Oct 9, 2026
Merged

michael-s-molina merged 3 commits into
apache:masterfrom
luizotavio32:fix/permalink-shared-lock-deadlock-master

Conversation

@luizotavio32

Copy link
Copy Markdown
Contributor

SUMMARY

After #45059, bursts of 3 or more identical permalink requests for the same dashboard can still return 500 on MySQL. Each request that loses the race already holds a shared lock on the duplicate row, then asks for an exclusive lock to re-read it. The losers end up waiting on each other and MySQL aborts one of them with a deadlock (1213 Deadlock found when trying to get lock). Two concurrent requests don't trigger it.

This change makes that re-read take a shared lock instead. Shared locks don't conflict with each other, so the deadlock goes away, and the re-read still sees the row the winning request just committed. The exclusive lock used by the distributed-lock release is unchanged.

TESTING INSTRUCTIONS

Unit tests added and updated.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

🤖 Generated with Claude Code

@netlify

netlify Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit e3e60fc
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6ac9410567d5080008d2df4e
😎 Deploy Preview https://deploy-preview-45102--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

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

@codecov

codecov Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.83%. Comparing base (a8fc576) to head (e3e60fc).

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #45102   +/-   ##
=======================================
  Coverage   82.83%   82.83%           
=======================================
  Files        3017     3017           
  Lines      192698   192709   +11     
  Branches    44914    44914           
=======================================
+ Hits       159624   159635   +11     
  Misses      30040    30040           
  Partials     3034     3034           
Flag Coverage Δ
hive 35.35% <70.58%> (+<0.01%) ⬆️
mysql 53.80% <94.11%> (+<0.01%) ⬆️
postgres 53.81% <94.11%> (+<0.01%) ⬆️
presto 37.19% <70.58%> (+<0.01%) ⬆️
python 86.47% <100.00%> (+<0.01%) ⬆️
sqlite 53.55% <94.11%> (+<0.01%) ⬆️
unit 80.11% <100.00%> (+<0.01%) ⬆️

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.

@luizotavio32
luizotavio32 marked this pull request as ready for review October 8, 2026 18:32
@luizotavio32

Copy link
Copy Markdown
Contributor Author

/review

@codeant-ai-for-open-source

codeant-ai-for-open-source Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

CodeAnt PR Risk: Low Risk

  • The PR appears safe to merge; permalink duplicate-key retries now use shared locks to avoid deadlocks between concurrent losers on InnoDB.
  • Distributed-lock release retains an exclusive lock for the ownership check and delete, and tests verify the shared-lock SQL for MySQL and PostgreSQL.

Assessed commit: ea8bb63626ce

@michael-s-molina michael-s-molina 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.

Thanks for tracking this down. The shared-lock re-read makes sense: a locking read still returns the winner's committed row under REPEATABLE READ, and shared locks don't conflict with each other, so the 3+ loser deadlock goes away.

Suggestion: replace the booleans with a lock-options object

get_entry now has two mutually exclusive flags, for_update and for_share. Nothing enforces that. With if/elif, passing both silently ignores for_share. Every new lock mode (nowait, skip_locked, ...) would also need another boolean. Query.with_for_update already takes several arguments, so I'd model them directly:

@dataclass(frozen=True)
class RowLock:
    read: bool = False         # shared lock (FOR SHARE / LOCK IN SHARE MODE)
    nowait: bool = False
    skip_locked: bool = False
    key_share: bool = False    # Postgres only

    def apply(self, query: Query) -> Query:
        return query.with_for_update(
            read=self.read,
            nowait=self.nowait,
            skip_locked=self.skip_locked,
            key_share=self.key_share,
        )

def get_entry(cls, resource, key, lock: RowLock | None = None) -> KeyValueEntry | None:
    query = db.session.query(KeyValueEntry).filter_by(**get_filter(resource, key))
    if lock is not None:
        query = lock.apply(query)
    return query.first()

@pull-request-size pull-request-size Bot added size/L and removed size/M labels Oct 9, 2026
luizotavio32 and others added 3 commits October 9, 2026 16:29
…ted permalink

On InnoDB, each request that loses the duplicate-key race already holds a
shared lock on the duplicate index record. With 3 or more concurrent identical
requests, their exclusive FOR UPDATE re-reads wait on each other's shared locks
and InnoDB aborts one with a deadlock, so the request still returns 500.

Re-read the winner's row with a shared locking read instead. Shared locks are
compatible with each other, and a locking read still sees the latest committed
row under REPEATABLE READ. Nothing after the re-read writes to the row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@luizotavio32
luizotavio32 force-pushed the fix/permalink-shared-lock-deadlock-master branch from ea8bb63 to e3e60fc Compare October 9, 2026 19:31
@michael-s-molina
michael-s-molina merged commit cb8ff28 into apache:master Oct 9, 2026
78 of 79 checks passed
@luizotavio32
luizotavio32 deleted the fix/permalink-shared-lock-deadlock-master branch October 9, 2026 20:15
michael-s-molina pushed a commit that referenced this pull request Oct 9, 2026
…ted permalink (#45102)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit cb8ff28)

Backport notes: dropped the hunks for ReportConfigDAO, the ownership-checked
KV lock release and their tests, which do not exist on 6.2.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.

2 participants