From 6c6137f9f2aef15ab9216bb3aa3eb2bdc71e32cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 13 May 2026 11:18:05 +0400 Subject: [PATCH] Fix deadlock when updating tracker pattern last_matched_at MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace per-row UPDATE inside the detected-tracker loop with a single bulk UPDATE ... WHERE id = ANY(...) after the loop. The old approach locked pattern rows in request-dependent order, causing deadlocks under concurrent ReportDetectedTrackers calls. Signed-off-by: Émile Ré --- pkg/cookiebanner/service.go | 17 ++++++++++----- pkg/coredata/tracker_pattern.go | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/pkg/cookiebanner/service.go b/pkg/cookiebanner/service.go index d4c1a7cf1..0cd779e20 100644 --- a/pkg/cookiebanner/service.go +++ b/pkg/cookiebanner/service.go @@ -2023,6 +2023,7 @@ func (s *Service) ReportDetectedTrackers( inserted := 0 now := time.Now() + var matchedPatternIDs []gid.GID for _, dc := range req.Cookies { if err := s.reportDetectedTracker( @@ -2040,6 +2041,7 @@ func (s *Service) ReportDetectedTrackers( InitiatorURL: dc.InitiatorURL, }, &inserted, + &matchedPatternIDs, ); err != nil { return err } @@ -2060,6 +2062,7 @@ func (s *Service) ReportDetectedTrackers( InitiatorURL: ds.InitiatorURL, }, &inserted, + &matchedPatternIDs, ); err != nil { return err } @@ -2083,6 +2086,13 @@ func (s *Service) ReportDetectedTrackers( } } + if len(matchedPatternIDs) > 0 { + var patterns coredata.TrackerPatterns + if err := patterns.UpdateLastMatchedAt(ctx, tx, scope, matchedPatternIDs, now); err != nil { + return fmt.Errorf("cannot update tracker pattern last_matched_at: %w", err) + } + } + if inserted > 0 { if err := banner.SetPatternAnalysisRequested(ctx, tx); err != nil { return fmt.Errorf("cannot request pattern analysis: %w", err) @@ -2112,6 +2122,7 @@ func (s *Service) reportDetectedTracker( now time.Time, info detectedTrackerInfo, inserted *int, + matchedPatternIDs *[]gid.GID, ) error { var matchedPattern coredata.TrackerPattern err := matchedPattern.FindMatchingPattern(ctx, tx, scope, banner.ID, info.TrackerType, info.Identifier) @@ -2126,11 +2137,7 @@ func (s *Service) reportDetectedTracker( var patternID *gid.GID if err == nil { patternID = &matchedPattern.ID - matchedPattern.LastMatchedAt = &now - matchedPattern.UpdatedAt = now - if updateErr := matchedPattern.Update(ctx, tx, scope); updateErr != nil { - return fmt.Errorf("cannot update tracker pattern last_matched_at: %w", updateErr) - } + *matchedPatternIDs = append(*matchedPatternIDs, matchedPattern.ID) } else { newPattern := &coredata.TrackerPattern{ ID: gid.New(scope.GetTenantID(), coredata.TrackerPatternEntityType), diff --git a/pkg/coredata/tracker_pattern.go b/pkg/coredata/tracker_pattern.go index 4667c8ad4..c1662eb40 100644 --- a/pkg/coredata/tracker_pattern.go +++ b/pkg/coredata/tracker_pattern.go @@ -801,6 +801,44 @@ WHERE return count, nil } +func (tps *TrackerPatterns) UpdateLastMatchedAt( + ctx context.Context, + tx pg.Tx, + scope Scoper, + patternIDs []gid.GID, + matchedAt time.Time, +) error { + if len(patternIDs) == 0 { + return nil + } + + q := ` +UPDATE tracker_patterns +SET + last_matched_at = @matched_at, + updated_at = @updated_at +WHERE + %s + AND id = ANY(@pattern_ids) +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{ + "pattern_ids": patternIDs, + "matched_at": matchedAt, + "updated_at": matchedAt, + } + maps.Copy(args, scope.SQLArguments()) + + _, err := tx.Exec(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot update last_matched_at for tracker patterns: %w", err) + } + + return nil +} + func (tps *TrackerPatterns) MoveToCategoryByCookieCategoryID( ctx context.Context, tx pg.Tx,