Skip to content

Commit 8b0a1aa

Browse files
authored
[cache] Support async RangeMissingHandler callbacks (#111340) (#111896)
Change `fillCacheRange` method to accept a completion listener that must be called by `RangeMissingHandler` implementations when they finish fetching data. By doing so, we support asynchronously fetching the data from a third party storage. We also support asynchronous `SourceInputStreamFactory` for reading gaps from the storage.
1 parent 7bf730a commit 8b0a1aa

File tree

3 files changed

+253
-123
lines changed

3 files changed

+253
-123
lines changed

x-pack/plugin/blob-cache/src/main/java/org/elasticsearch/blobcache/shared/SharedBlobCacheService.java

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -650,13 +650,14 @@ private RangeMissingHandler writerWithOffset(RangeMissingHandler writer, int wri
650650
// no need to allocate a new capturing lambda if the offset isn't adjusted
651651
return writer;
652652
}
653-
return (channel, channelPos, streamFactory, relativePos, len, progressUpdater) -> writer.fillCacheRange(
653+
return (channel, channelPos, streamFactory, relativePos, len, progressUpdater, completionListener) -> writer.fillCacheRange(
654654
channel,
655655
channelPos,
656656
streamFactory,
657657
relativePos - writeOffset,
658658
len,
659-
progressUpdater
659+
progressUpdater,
660+
completionListener
660661
);
661662
}
662663

@@ -991,16 +992,17 @@ void populateAndRead(
991992
executor.execute(fillGapRunnable(gap, writer, null, refs.acquireListener()));
992993
}
993994
} else {
994-
final List<AbstractRunnable> gapFillingTasks = gaps.stream()
995-
.map(gap -> fillGapRunnable(gap, writer, streamFactory, refs.acquireListener()))
996-
.toList();
997-
executor.execute(() -> {
998-
try (streamFactory) {
995+
var gapFillingListener = refs.acquireListener();
996+
try (var gfRefs = new RefCountingRunnable(ActionRunnable.run(gapFillingListener, streamFactory::close))) {
997+
final List<Runnable> gapFillingTasks = gaps.stream()
998+
.map(gap -> fillGapRunnable(gap, writer, streamFactory, gfRefs.acquireListener()))
999+
.toList();
1000+
executor.execute(() -> {
9991001
// Fill the gaps in order. If a gap fails to fill for whatever reason, the task for filling the next
10001002
// gap will still be executed.
10011003
gapFillingTasks.forEach(Runnable::run);
1002-
}
1003-
});
1004+
});
1005+
}
10041006
}
10051007
}
10061008
}
@@ -1009,13 +1011,13 @@ void populateAndRead(
10091011
}
10101012
}
10111013

1012-
private AbstractRunnable fillGapRunnable(
1014+
private Runnable fillGapRunnable(
10131015
SparseFileTracker.Gap gap,
10141016
RangeMissingHandler writer,
10151017
@Nullable SourceInputStreamFactory streamFactory,
10161018
ActionListener<Void> listener
10171019
) {
1018-
return ActionRunnable.run(listener.delegateResponse((l, e) -> failGapAndListener(gap, l, e)), () -> {
1020+
return () -> ActionListener.run(listener, l -> {
10191021
var ioRef = io;
10201022
assert regionOwners.get(ioRef) == CacheFileRegion.this;
10211023
assert CacheFileRegion.this.hasReferences() : CacheFileRegion.this;
@@ -1026,10 +1028,15 @@ private AbstractRunnable fillGapRunnable(
10261028
streamFactory,
10271029
start,
10281030
Math.toIntExact(gap.end() - start),
1029-
progress -> gap.onProgress(start + progress)
1031+
progress -> gap.onProgress(start + progress),
1032+
l.<Void>map(unused -> {
1033+
assert regionOwners.get(ioRef) == CacheFileRegion.this;
1034+
assert CacheFileRegion.this.hasReferences() : CacheFileRegion.this;
1035+
writeCount.increment();
1036+
gap.onCompletion();
1037+
return null;
1038+
}).delegateResponse((delegate, e) -> failGapAndListener(gap, delegate, e))
10301039
);
1031-
writeCount.increment();
1032-
gap.onCompletion();
10331040
});
10341041
}
10351042

@@ -1117,12 +1124,23 @@ public void fillCacheRange(
11171124
SourceInputStreamFactory streamFactory,
11181125
int relativePos,
11191126
int length,
1120-
IntConsumer progressUpdater
1127+
IntConsumer progressUpdater,
1128+
ActionListener<Void> completionListener
11211129
) throws IOException {
1122-
writer.fillCacheRange(channel, channelPos, streamFactory, relativePos, length, progressUpdater);
1123-
var elapsedTime = TimeUnit.NANOSECONDS.toMillis(relativeTimeInNanosSupplier.getAsLong() - startTime);
1124-
SharedBlobCacheService.this.blobCacheMetrics.getCacheMissLoadTimes().record(elapsedTime);
1125-
SharedBlobCacheService.this.blobCacheMetrics.getCacheMissCounter().increment();
1130+
writer.fillCacheRange(
1131+
channel,
1132+
channelPos,
1133+
streamFactory,
1134+
relativePos,
1135+
length,
1136+
progressUpdater,
1137+
completionListener.map(unused -> {
1138+
var elapsedTime = TimeUnit.NANOSECONDS.toMillis(relativeTimeInNanosSupplier.getAsLong() - startTime);
1139+
blobCacheMetrics.getCacheMissLoadTimes().record(elapsedTime);
1140+
blobCacheMetrics.getCacheMissCounter().increment();
1141+
return null;
1142+
})
1143+
);
11261144
}
11271145
};
11281146
if (rangeToRead.isEmpty()) {
@@ -1215,9 +1233,18 @@ public void fillCacheRange(
12151233
SourceInputStreamFactory streamFactory,
12161234
int relativePos,
12171235
int len,
1218-
IntConsumer progressUpdater
1236+
IntConsumer progressUpdater,
1237+
ActionListener<Void> completionListener
12191238
) throws IOException {
1220-
delegate.fillCacheRange(channel, channelPos, streamFactory, relativePos - writeOffset, len, progressUpdater);
1239+
delegate.fillCacheRange(
1240+
channel,
1241+
channelPos,
1242+
streamFactory,
1243+
relativePos - writeOffset,
1244+
len,
1245+
progressUpdater,
1246+
completionListener
1247+
);
12211248
}
12221249
};
12231250
}
@@ -1230,14 +1257,25 @@ public void fillCacheRange(
12301257
SourceInputStreamFactory streamFactory,
12311258
int relativePos,
12321259
int len,
1233-
IntConsumer progressUpdater
1260+
IntConsumer progressUpdater,
1261+
ActionListener<Void> completionListener
12341262
) throws IOException {
12351263
assert assertValidRegionAndLength(fileRegion, channelPos, len);
1236-
delegate.fillCacheRange(channel, channelPos, streamFactory, relativePos, len, progressUpdater);
1237-
assert regionOwners.get(fileRegion.io) == fileRegion
1238-
: "File chunk [" + fileRegion.regionKey + "] no longer owns IO [" + fileRegion.io + "]";
1264+
delegate.fillCacheRange(
1265+
channel,
1266+
channelPos,
1267+
streamFactory,
1268+
relativePos,
1269+
len,
1270+
progressUpdater,
1271+
Assertions.ENABLED ? ActionListener.runBefore(completionListener, () -> {
1272+
assert regionOwners.get(fileRegion.io) == fileRegion
1273+
: "File chunk [" + fileRegion.regionKey + "] no longer owns IO [" + fileRegion.io + "]";
1274+
}) : completionListener
1275+
);
12391276
}
12401277
};
1278+
12411279
}
12421280
return adjustedWriter;
12431281
}
@@ -1324,14 +1362,16 @@ default SourceInputStreamFactory sharedInputStreamFactory(List<SparseFileTracker
13241362
* @param length of data to fetch
13251363
* @param progressUpdater consumer to invoke with the number of copied bytes as they are written in cache.
13261364
* This is used to notify waiting readers that data become available in cache.
1365+
* @param completionListener listener that has to be called when the callback method completes
13271366
*/
13281367
void fillCacheRange(
13291368
SharedBytes.IO channel,
13301369
int channelPos,
13311370
@Nullable SourceInputStreamFactory streamFactory,
13321371
int relativePos,
13331372
int length,
1334-
IntConsumer progressUpdater
1373+
IntConsumer progressUpdater,
1374+
ActionListener<Void> completionListener
13351375
) throws IOException;
13361376
}
13371377

@@ -1343,9 +1383,9 @@ public interface SourceInputStreamFactory extends Releasable {
13431383
/**
13441384
* Create the input stream at the specified position.
13451385
* @param relativePos the relative position in the remote storage to read from.
1346-
* @return the input stream ready to be read from.
1386+
* @param listener listener for the input stream ready to be read from.
13471387
*/
1348-
InputStream create(int relativePos) throws IOException;
1388+
void create(int relativePos, ActionListener<InputStream> listener) throws IOException;
13491389
}
13501390

13511391
private abstract static class DelegatingRangeMissingHandler implements RangeMissingHandler {
@@ -1367,9 +1407,10 @@ public void fillCacheRange(
13671407
SourceInputStreamFactory streamFactory,
13681408
int relativePos,
13691409
int length,
1370-
IntConsumer progressUpdater
1410+
IntConsumer progressUpdater,
1411+
ActionListener<Void> completionListener
13711412
) throws IOException {
1372-
delegate.fillCacheRange(channel, channelPos, streamFactory, relativePos, length, progressUpdater);
1413+
delegate.fillCacheRange(channel, channelPos, streamFactory, relativePos, length, progressUpdater, completionListener);
13731414
}
13741415
}
13751416

0 commit comments

Comments
 (0)