Repository navigation
Conversation
|
Claude finished @swicken's task in 2m 9s —— View job Code ReviewReviewed the diff against Existing (prior dotbot findings — still present)
Notes (not blocking)
No new issues beyond the two carried-forward findings. · |
badcb59 to
959df10
Compare
959df10 to
666a133
Compare
666a133 to
7e8397b
Compare
| } else { | ||
| // Non-FILE_SYSTEM mode: use storage API | ||
| final String sourcePath = buildFilePath(sourceInode, fieldVarName, fileName); | ||
| final File sourceFile = resolveContentReferences && com.dotcms.storage.AssetStorageFeature.isEnabled() |
There was a problem hiding this comment.
�f7e1 [P2] BinaryAssetStorageAPIImpl.java:543 guard missing source in copyBinary
Current code:
final File sourceFile = resolveContentReferences && com.dotcms.storage.AssetStorageFeature.isEnabled()
? getBinaryFile(sourceInode, fieldVarName, fileName)
: storagePersistenceAPI.pullFile(BINARY_ASSETS_GROUP, sourcePath);
final String destPath = buildFilePath(destInode, fieldVarName, fileName);
storagePersistenceAPI.pushFile(BINARY_ASSETS_GROUP, destPath, sourceFile, Map.<String, Serializable>of());Problem: Missing source returns null, then push dereferences it opaquely.
Fix:
final File sourceFile = resolveContentReferences && com.dotcms.storage.AssetStorageFeature.isEnabled()
? getBinaryFile(sourceInode, fieldVarName, fileName)
: storagePersistenceAPI.pullFile(BINARY_ASSETS_GROUP, sourcePath);
if (sourceFile == null) {
throw new DotDataException(String.format("Binary not found for inode '%s', field '%s', file '%s'", sourceInode, fieldVarName, fileName));
}
final String destPath = buildFilePath(destInode, fieldVarName, fileName);
storagePersistenceAPI.pushFile(BINARY_ASSETS_GROUP, destPath, sourceFile, Map.<String, Serializable>of());| } else { | ||
| // Non-FILE_SYSTEM mode: use storage API | ||
| final String sourcePath = buildFilePath(sourceInode, fieldVarName, fileName); | ||
| final File sourceFile = resolveContentReferences && com.dotcms.storage.AssetStorageFeature.isEnabled() |
There was a problem hiding this comment.
🟡 [P2] BinaryAssetStorageAPIImpl.java:543 guard missing source in copyBinary
Current code:
final File sourceFile = resolveContentReferences && com.dotcms.storage.AssetStorageFeature.isEnabled()
? getBinaryFile(sourceInode, fieldVarName, fileName)
: storagePersistenceAPI.pullFile(BINARY_ASSETS_GROUP, sourcePath);
final String destPath = buildFilePath(destInode, fieldVarName, fileName);
storagePersistenceAPI.pushFile(BINARY_ASSETS_GROUP, destPath, sourceFile,
Map.<String, Serializable>of());Problem: Both getBinaryFile and pullFile return null when the source field has no binary; pushFile then dereferences it (S3ContentAddressedStorage.store opens source.toPath()), producing an opaque NPE wrapped as DotDataException.
Fix:
final String destPath = buildFilePath(destInode, fieldVarName, fileName);
if (sourceFile == null || !sourceFile.exists()) {
throw new DotDataException(String.format(
"Source binary does not exist for inode '%s', field '%s', file '%s'",
sourceInode, fieldVarName, fileName));
}
storagePersistenceAPI.pushFile(BINARY_ASSETS_GROUP, destPath, sourceFile,
Map.<String, Serializable>of());The filesystem branch already fails with a clear DotDataException on the same missing-source input; this makes both modes consistent and descriptive.
| return; | ||
| } | ||
| DotConcurrentFactory.getScheduledThreadPoolExecutor().schedule(() -> { | ||
| try { |
There was a problem hiding this comment.
⚪ [P3] DotInitScheduler.java:619 eviction chain cannot be cancelled and duplicates if start() re-enters
Current code:
} finally {
scheduleBinaryCacheEviction(cron, new Date());
}
}, Math.max(0, next.getTime() - System.currentTimeMillis()), TimeUnit.MILLISECONDS);Problem: scheduleBinaryCacheEviction self-reschedules onto the shared scheduled pool with no retained ScheduledFuture or started-guard. If DotInitScheduler.start() is ever re-entered, each entry spawns an independent, uncancellable eviction chain for the JVM lifetime.
Fix:
private static final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.ScheduledFuture<?>> BINARY_EVICTION_CHAIN =
new java.util.concurrent.atomic.AtomicReference<>();
private static void scheduleBinaryCacheEviction(final CronExpression cron, final Date after) {
final Date next = cron.getNextValidTimeAfter(after);
if (next == null) {
Logger.warn(DotInitScheduler.class, "BinaryCacheEvictionJob cron expression has no future run: " + cron);
return;
}
final java.util.concurrent.ScheduledFuture<?> future = DotConcurrentFactory.getScheduledThreadPoolExecutor().schedule(() -> {
try {
new BinaryCacheEvictionJob().execute(null);
} catch (final Exception e) {
Logger.warnAndDebug(DotInitScheduler.class, e);
} finally {
scheduleBinaryCacheEviction(cron, new Date());
}
}, Math.max(0, next.getTime() - System.currentTimeMillis()), TimeUnit.MILLISECONDS);
BINARY_EVICTION_CHAIN.set(future);
}Assumption: production InitServlet calls start() once per JVM, so impact today is low; the guard makes it robust if that guarantee ever changes.
… S3 asset storage Second slice of the S3 asset storage work. Adds BinaryAssetStorageAPI for binary assets and completed renditions, content-addressed S3 blobs for those two groups with mixed-case keys, cache leases, and the scheduled BinaryCacheEvictionJob that evicts local files only when S3 holds a verified copy. Nothing calls the API from content code yet, and everything is inert while FEATURE_FLAG_S3_ASSET_STORAGE is off.
…inary asset API
Eviction no longer holds the process-wide write lock while it verifies the
S3 copy. The path checks and the durable-copy verification run with no lock
held, after recording the file's attributes; the write lock is then taken
without waiting only to repeat the path checks, confirm the attributes are
unchanged, and delete. A cache lease requested during a slow S3 download is
granted at once.
BinaryCacheEvictionJob now runs on every node from the node-local scheduled
thread pool, following BINARY_CACHE_EVICTION_CRON, instead of through the
clustered Quartz store, which fired it on only one node per run. A copy
stored in Quartz by an earlier build is removed at startup.
A contentlet row without contentlet_as_json (JSON persistence off, or not yet
populated) now falls back to the older inode/field/fileName lookup in both
getBinaryFile overloads and existsBinary, instead of reporting that the
binary is missing.
The older-layout methods now reject an inode that is not at least two
letters, digits, underscores or hyphens, and a field or file name that is
blank, contains a separator, or is a dot segment. deleteLocalInodeDirectory
also refuses a directory that is not three levels below the asset root, so
deleteAllBinaries("..") can no longer delete the parent of the asset root.
The older-layout methods take the eviction read lock only when S3 asset
storage is on, through acquireCacheLease, and ensureGeneratedGroupExists
gets the same volatile fast path as ensureGroupExists.
Metadata generation for a binary that is neither cached nor stored durably
gives the flag-off results again (empty raw metadata, IllegalArgumentException
on the configured path) instead of a DotRuntimeException. Failed restores
still throw.
The storage doc now describes the durable-copy proof actually used for
binary and generated assets, per-node scheduling, the lock-free
verification, the contentlet_as_json fallback and input validation, and no
longer claims that NFS-backed asset directories never evict.
801072a to
aa4cc7a
Compare
7e8397b to
14d49ce
Compare
|
test |
|
Pull Request Unsafe to Rollback!!!
|
# Conflicts: # dotCMS/src/main/java/com/dotcms/storage/AmazonS3StoragePersistenceAPIImpl.java
|
dotbot code review:
No new actionable bugs were found in the current changes, but 2 prior unresolved dotbot findings still apply, so the patch remains incorrect. Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads. reviewed by dotbot · meta/muse-spark-1.3 · medium |
|
dotbot code review:
No new actionable bugs were found in the current changes, but 2 prior unresolved dotbot findings still apply, so the patch remains incorrect. Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads. reviewed by dotbot · ~z-ai/glm-latest · medium |
Refs #37868
Proposed Changes
BinaryAssetStorageAPI(APILocator.getBinaryAssetStorageAPI()) manages binary assets (binary-assetsgroup) and completed renditions (generated-assetsgroup) through the storage chain. With the flag on, keys in these two groups keep their mixed-case names; other groups are still lowercased (FileSystemStoragePersistenceAPIImpl.normalizePath). Inode, field and file names are validated, and directory deletes must resolve under the asset root.FileStorageAPIImpluses one while metadata generation needs an evicted original's bytes.BinaryCacheEvictionJobtrims each node's local cache, scheduled on every node from the node-local scheduled pool only whenBINARY_CACHE_EVICTION_CRONis set. A file is evicted only when S3 holds a verified copy at the exact key (the reference's SHA-256 must match the local file, then the blob's bytes are compared). The verification runs without any lock; the write lock is taken only to re-check the file's attributes and delete it, so a slow S3 never blocks reads.Behavior with the flag off
Unchanged from main. The API uses the existing filesystem/NFS paths and makes no remote calls, the eviction job is not scheduled, and the scheduler logs nothing new. The name validation also applies with the flag off, where it only rejects invalid input; nothing in dotCMS calls these methods with the flag off.
Review fixes
The last commit on this branch (
fix(storage): verify eviction without the cache lock and harden the binary asset API) addresses a full review of this PR:contentlet_as_jsonis null or blank falls back to the older lookup instead of reporting its binaries as missing.deleteAllBinaries("..")can no longer delete the parent of the asset root.Deliberately not in this PR
No content code calls the API yet; check-in, reads and cleanup arrive in 2. The
storageKeyfields on the binary field type also move to 2, because only 2 reads them.Checklist
dotGeneratedroots, and files without a verified remote copy. Inode, field and file names are validated before any path is built.