Skip to content

Commit

Permalink
Blobstore can throw FileAlreadyExistsException #10447 (#10831)
Browse files Browse the repository at this point in the history
(cherry picked from commit bb854e1)
  • Loading branch information
rymsha committed Dec 23, 2024
1 parent 161516c commit d2ccf39
Show file tree
Hide file tree
Showing 13 changed files with 240 additions and 254 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -172,11 +172,14 @@ private BlobRecord addRecord( final Segment segment, final BlobKey key, final By
if ( !Files.exists( file ) )
{
Files.createDirectories( file.getParent() );
in.copyTo( MoreFiles.asByteSink( file ) );
}
else
{
Files.setLastModifiedTime( file, FileTime.fromMillis( System.currentTimeMillis() ) );
try (var inStream = in.openStream())
{
Files.copy( inStream, file );
}
catch ( FileAlreadyExistsException e )
{
LOG.debug( "File already exists [{}]", file, e );
}
}

return new FileBlobRecord( key, file );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class FileBlobStoreTest
{
Expand Down Expand Up @@ -120,18 +119,6 @@ public void deleteSegment()
assertEquals( segment, blobStore.listSegments().findFirst().get() );
}

@Test
public void lastModifiedUpdated()
throws Exception
{
final BlobRecord rec1 = this.blobStore.addRecord( this.segment, ByteSource.wrap( "hello".getBytes() ) );
final long beforeUpdate = rec1.lastModified();
Thread.sleep( 1000 ); // ensure a second difference
final BlobRecord rec2 = this.blobStore.addRecord( this.segment, ByteSource.wrap( "hello".getBytes() ) );

assertTrue( beforeUpdate < rec2.lastModified() );
}

private BlobRecord createRecord( final String str )
{
return createRecord( segment, str );
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.enonic.xp.internal.blobstore.cache;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Objects;
import java.util.stream.Stream;

import org.slf4j.Logger;
Expand Down Expand Up @@ -76,7 +78,6 @@ public void removeRecord( final Segment segment, final BlobKey key )
throws BlobStoreException
{
this.store.removeRecord( segment, key );
this.cache.invalidate( key );
}

@Override
Expand All @@ -87,21 +88,32 @@ public void invalidate( final Segment segment, final BlobKey key )

private BlobRecord addToCache( final BlobRecord record )
{
if ( record.getLength() <= this.sizeThreshold )
// Quick check to avoid caching large blobs
if ( record.getLength() > this.sizeThreshold )
{
try
{
final CacheBlobRecord cacheBlobRecord = new CacheBlobRecord( record );
this.cache.put( record.getKey(), cacheBlobRecord );
return cacheBlobRecord;
}
catch ( IOException e )
{
LOG.error( "Could not create cache blob-record", e );
}
return record;
}

// Don't do heavy lifting if the blob is already in the cache
final BlobKey key = record.getKey();
final CacheBlobRecord present = this.cache.getIfPresent( key );
if ( present != null )
{
return present;
}

return record;
try
{
// We don't want return blobs that are not in cache - as we want them to garbage collected ASAP,
// so we dont use get() with loader here
this.cache.put( key, new CacheBlobRecord( record ) );
}
catch ( IOException e )
{
LOG.warn( "Could not cache blob-record with key {}", key, e );
}
// If we could not load the blob into cache, we return the original record
return Objects.requireNonNullElse( this.cache.getIfPresent( key ), record );
}

@Override
Expand Down Expand Up @@ -171,4 +183,58 @@ public int weigh( final BlobKey key, final BlobRecord record )
return (int) Math.min( record.getLength(), Integer.MAX_VALUE );
}
}

private static final class CacheBlobRecord
implements BlobRecord
{
final BlobKey blobKey;

final long lastModified;

final ByteSource content;

CacheBlobRecord( final BlobRecord blobRecord )
throws IOException
{
this.content = ByteSource.wrap( blobRecord.getBytes().read() );
this.blobKey = BlobKey.from( this.content );
if ( !blobRecord.getKey().equals( this.blobKey ) )
{
throw new IOException( String.format( "Cache BlobKey must be the same as the key of the BlobRecord: %s != %s", this.blobKey,
blobRecord.getKey() ) );
}
this.lastModified = blobRecord.lastModified();
}

@Override
public BlobKey getKey()
{
return this.blobKey;
}

@Override
public long getLength()
{
try
{
return content.size();
}
catch ( IOException e )
{
throw new UncheckedIOException( e );
}
}

@Override
public ByteSource getBytes()
{
return this.content;
}

@Override
public long lastModified()
{
return this.lastModified;
}
}
}
Loading

0 comments on commit d2ccf39

Please sign in to comment.