Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,13 @@ private boolean transferArchive(
long received = 0;
int currentPercentage = 0;
long lastPostedBytes = baseOffset;
long lastFreeSpaceProbeBytes = 0;
// Unknown length: the response-time check could only reserve the
// margin, so the disk is probed before any write that would run
// past the bytes reserved so far, each probe reserving at least
// the next PROBE bytes (more when one chunk is larger) — no write
// can ever eat into the margin (the archive cap alone is far more
// than the margin). A throw keeps the partial for a later attempt.
long freeSpaceReservedUntil = 0;

try (
BufferedSource source = body.source();
Expand All @@ -361,6 +367,20 @@ private boolean transferArchive(
) {
while ((bytesRead = source.read(sink.buffer(), DOWNLOAD_CHUNK_SIZE)) != -1) {
received += bytesRead;
if (totalAll <= 0 && received > freeSpaceReservedUntil) {
long reserve = Math.max(
ArchiveLimits.UNKNOWN_LENGTH_FREE_SPACE_PROBE_BYTES, bytesRead);
freeSpaceReservedUntil = received - bytesRead + reserve;
try {
ArchiveLimits.ensureFreeSpace(writePath, reserve);
} catch (IOException e) {
// The chunk is still only buffered; closing the
// sink would flush it onto the disk that just
// failed the probe.
sink.buffer().clear();
throw e;
}
}
sink.emit();

long overall = baseOffset + received;
Expand All @@ -369,15 +389,6 @@ private boolean transferArchive(
throw new IOException(
"archive too large: exceeded " + ArchiveLimits.MAX_ARCHIVE_BYTES);
}
if (totalAll <= 0 && received - lastFreeSpaceProbeBytes
>= ArchiveLimits.UNKNOWN_LENGTH_FREE_SPACE_PROBE_BYTES) {
// Unknown length: the response-time check could only
// reserve the margin, so re-probe as bytes stream in
// (the archive cap alone is far more than the margin).
// A throw keeps the partial for a later attempt.
lastFreeSpaceProbeBytes = received;
ArchiveLimits.ensureFreeSpace(writePath, DOWNLOAD_CHUNK_SIZE);
}
if (totalAll > 0) {
int percentage = (int) (overall * 100.0 / totalAll + 0.5);
if (percentage > currentPercentage) {
Expand Down
4 changes: 3 additions & 1 deletion cpp/patch_core/archive_limits.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ constexpr long long kMaxManifestBytes = 16LL * 1024 * 1024;
constexpr long long kFreeDiskMarginBytes = 64LL * 1024 * 1024;
// A download whose length is unknown up front (chunked / encoded body) can
// only reserve the margin when the response arrives; the disk is re-probed
// every this many streamed bytes so the cap above cannot eat the margin.
// before any write that would run past the bytes reserved so far, each
// probe reserving at least this many bytes ahead (a single larger chunk
// reserves its own size), so no write can ever eat into the margin.
constexpr long long kUnknownLengthFreeSpaceProbeBytes = 8LL * 1024 * 1024;

} // namespace archive_limits
Expand Down
22 changes: 13 additions & 9 deletions harmony/pushy/src/main/ets/DownloadTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ export class DownloadTask {
}
};

let lastFreeSpaceProbeBytes = 0;
let freeSpaceReservedUntil = 0;
const enqueueWrite = (data: ArrayBuffer) => {
received += data.byteLength;
if (!writeError && baseOffset + received > MAX_ARCHIVE_BYTES) {
Expand All @@ -689,22 +689,26 @@ export class DownloadTask {
`archive too large: exceeded ${MAX_ARCHIVE_BYTES}`,
);
}
const probeFreeSpace =
totalAll <= 0 &&
received - lastFreeSpaceProbeBytes >=
UNKNOWN_LENGTH_FREE_SPACE_PROBE_BYTES;
// 未知长度:响应到达时只能预留安全余量,所以任何会写过已预留字节数的
// 写入之前先探测,每次至少预留接下来的 PROBE 字节(单个 chunk 更大时按
// chunk 算)——任何写入都吃不到余量(归档上限本身远大于余量)。失败保留
// partial 供下次续传。
const writtenBefore = received - data.byteLength;
const probeFreeSpace = totalAll <= 0 && received > freeSpaceReservedUntil;
const reserve = Math.max(
UNKNOWN_LENGTH_FREE_SPACE_PROBE_BYTES,
data.byteLength,
);
if (probeFreeSpace) {
lastFreeSpaceProbeBytes = received;
freeSpaceReservedUntil = writtenBefore + reserve;
}
writeQueue = writeQueue.then(async () => {
if (!writer || writeError) {
return;
}
try {
if (probeFreeSpace) {
// 未知长度:响应到达时只能预留安全余量,边收边重新探测磁盘
// (归档上限本身远大于余量)。失败保留 partial 供下次续传。
await ensureFreeSpace(params.targetFile, data.byteLength);
await ensureFreeSpace(params.targetFile, reserve);
}
await fileIo.write(writer.fd, data);
} catch (error) {
Expand Down
25 changes: 15 additions & 10 deletions ios/RCTPushy/RCTPushyDownloader.mm
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@ @interface RCTPushyDownloader()<NSURLSessionDataDelegate>
@property (nonatomic, assign) BOOL discardPartial;
@property (nonatomic, assign) int lastReportedPercentage;
@property (nonatomic, assign) long long lastReportedBytes;
// receivedBytes at the last streaming free-space probe (unknown-length bodies)
@property (nonatomic, assign) long long lastFreeSpaceProbeBytes;
// receivedBytes up to which free disk has been probed for (unknown-length
// bodies; 0 = probe before the first write)
@property (nonatomic, assign) long long freeSpaceReservedUntil;
@end

@implementation RCTPushyDownloader
Expand Down Expand Up @@ -471,7 +472,7 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data
if (!append) {
[fileManager createFileAtPath:self.savePath contents:nil attributes:nil];
}
self.lastFreeSpaceProbeBytes = 0;
self.freeSpaceReservedUntil = 0;
self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.savePath];
if (self.fileHandle == nil) {
[self failWithDescription:@"cannot open download file for writing" code:-1];
Expand Down Expand Up @@ -523,14 +524,18 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data
return;
}
if (self.expectedTotal <= 0
&& self.receivedBytes - self.lastFreeSpaceProbeBytes
>= pushy::archive_limits::kUnknownLengthFreeSpaceProbeBytes) {
&& self.receivedBytes + (long long)data.length > self.freeSpaceReservedUntil) {
// Unknown/encoded length: the response-time check could only
// reserve the margin, so re-probe the disk as bytes stream in. The
// archive cap alone (512 MiB) is far more than the margin protects.
// The partial stays: a later attempt may find the space.
self.lastFreeSpaceProbeBytes = self.receivedBytes;
NSString *shortfall = RCTPushyFreeSpaceShortfall(self.savePath, data.length);
// reserve the margin, so probe before any write that would run past
// the bytes reserved so far, each probe reserving at least the next
// PROBE bytes (more when one callback carries a larger buffer) — no
// write can ever eat into the margin. The archive cap alone (512
// MiB) is far more than the margin protects. The partial stays: a
// later attempt may find the space.
long long reserve = MAX(pushy::archive_limits::kUnknownLengthFreeSpaceProbeBytes,
(long long)data.length);
self.freeSpaceReservedUntil = self.receivedBytes + reserve;
NSString *shortfall = RCTPushyFreeSpaceShortfall(self.savePath, reserve);
if (shortfall != nil) {
[self failWithDescription:shortfall code:-1];
[dataTask cancel];
Expand Down
Loading