Conversation
Two root causes fixed: 1. FixTask00090RecreateMissingFoldersInParentPath: replaced N×M per-segment SELECT COUNT(1) queries with a single upfront load of all folder identifier keys into a HashSet. isFolderIdentifierMissing() now uses O(1) Set.contains() lookups instead of individual DB round-trips. Newly created folders are added to the cache to prevent duplicate creation within the same run. 2. Task250604UpdateFolderInodes: executeFix() leaves a Hibernate thread-local transaction open (idle in transaction) via HibernateUtil.save() in createFixAudit(). This held locks that blocked fixFolderIds() ALTER TABLE from acquiring the exclusive lock on folder, exhausting the connection pool. Fixed by calling HibernateUtil.commitTransaction() + DbConnectionFactory .closeSilently() between the two steps. Verified against caliber-staging: 54 broken folders fixed in seconds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @dsilvam's task in 5m 13s —— View job Tasks
Review posted. Two issues found — see the 🔍 dotCMS Backend Review comment for details. |
…#35392) Three tests covering the N×M → O(1) cache fix: - creates missing intermediate folders for deep parent paths - idempotency: running twice produces no duplicates - skips folders that already exist in the identifier table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…MainSuite3a (#35392) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Pull Request Unsafe to Rollback!!!
|
🔍 dotCMS Backend Review[🟠 High]
// line 125 — guarded write (dead code: cache is never null in production)
if (folderKeyCache != null) {
folderKeyCache.add(folderKey(...));
}
// line 137 — unconditional dereference (NPE if null)
return !folderKeyCache.contains(folderKey(...));💡 Remove the null guard on line 125 and keep both paths unconditional — matching the actual contract. If null must be supported, add a symmetric guard in [🟡 Medium]
// loadExistingFolderKeys: PostgreSQL lower() via DB
keys.add(folderKey(rs.getString(1), rs.getString(2), rs.getString(3)));
// recreateMissingFolders: Java .toLowerCase() with JVM default locale
folderKeyCache.add(folderKey(folder.hostId, folder.parentPath.toLowerCase(), folder.name.toLowerCase()));💡 Replace Next steps
|
…ter-table-deadlock
- Replace manual setAutoCommit/commit/rollback in createFolder() with LocalTransaction.wrap() to avoid mutating the ThreadLocal connection's autoCommit state - Materialize outer ResultSet into a List before processing so the cursor is fully closed before any createFolder() call runs - Add WHERE asset_type <> 'folder' to outer scan to reduce scan scope - Use null-byte (\0) separator in folderKey() to eliminate pipe-character collision risk Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…them (#35392) The backward-compat private overloads (no-Set delegates) bypassed Mockito's proxy, causing unit test stubs on public methods to never be triggered. Collapse to a single @VisibleForTesting protected implementation per method that takes Set<String> as a required parameter. Move cache update from createFolder() into recreateMissingFolders() so createFolder stays spy-able with no Set parameter. Update unit tests to pass new HashSet<>() / any() for the Set argument. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…update (#35392) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eck trigger (#35392) The DB trigger blocks inserting any identifier whose parent folder doesn't exist yet, so tests can't manufacture broken state by direct INSERT. New approach: create the folder via API (valid state), insert the test contentlet inside it (now valid), delete the folder rows to simulate data corruption, then verify executeFix() recreates the missing identifier. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…egration tests (#35392) The INSERT trigger blocks creating identifiers with non-existent parent paths, and the DELETE trigger blocks removing folders that have children. Together they prevent manufacturing broken state via INSERT+DELETE. Fix: insert the contentlet at root (always valid), then UPDATE its parent_path to the non-existent folder path — UPDATE does not fire the parent path check trigger, achieving the needed corrupt state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…traints Replace the two tests that tried to manufacture corrupt DB state (broken by identifier_parent_path_check / folder_identifier_check triggers) with tests that call recreateMissingFoldersInParentPath() directly with an empty Set<String> cache. An empty cache simulates "folder identifier is missing" in memory without needing illegal SQL, keeping the tests valid while covering the real fix-task write path. Also add missing HashSet/Set imports. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mitTransaction Wrap HibernateUtil.commitTransaction() in try-finally so the ThreadLocal connection is always released even if commit throws. Without this, a DotHibernateException from commitTransaction would leave the connection open with held locks, causing fixFolderIds() ALTER TABLE to deadlock — the exact bug this PR fixes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Fixes #35392 —
Task250604UpdateFolderInodeshung indefinitely (20+ hours reported for customers db) when upgrading from 24.12.27 to currentmain.Two root causes fixed:
Bug 1 — N×M query storm in
FixTask00090RecreateMissingFoldersInParentPathexecuteFix()issued oneSELECT COUNT(1)per path segment per distinctparent_pathin theidentifiertableHashSet<String>(1 query). Replace DB lookups withO(1)Set.contains(). Newly created folders are added to the cache immediately to avoid re-creation.Bug 2 —
ALTER TABLEdeadlock infixFolderIds()fixFolderIds()runsALTER TABLE folder DROP/ADD CONSTRAINT ... DEFERRABLE(needs exclusive lock onfolder)executeFix()leaves a Hibernate thread-local transaction open (idle in transaction) viaHibernateUtil.save()increateFixAudit()— confirmed viapg_stat_activityHibernateUtil.commitTransaction()+DbConnectionFactory.closeSilently()betweenexecuteFix()andfixFolderIds()to release locks before DDL runsTest plan
inode ≠ identifier, SYSTEM_FOLDER broken)inode = identifier, SYSTEM_FOLDER correctTask250604UpdateFolderInodesTestintegration testsFiles Changed
dotCMS/src/main/java/com/dotmarketing/fixtask/tasks/FixTask00090RecreateMissingFoldersInParentPath.javadotCMS/src/main/java/com/dotmarketing/startup/runonce/Task250604UpdateFolderInodes.java🤖 Generated with Claude Code