chore:Fix the issue where the breakpoint resume receiver is missing one chunk of data

This commit is contained in:
david_bai
2025-09-14 11:29:51 +08:00
parent b5404cea72
commit 327de90f52
4 changed files with 153 additions and 109 deletions
+24 -38
View File
@@ -148,9 +148,10 @@ export class ChunkProcessor {
const startChunkIndex = ReceptionConfig.getChunkIndexFromOffset(initialOffset); // Resume start index
const relativeChunkIndex = absoluteChunkIndex - startChunkIndex; // Relative index in chunks array
if (ReceptionConfig.DEBUG_CONFIG.ENABLE_CHUNK_LOGGING && absoluteChunkIndex <= 970) {
// 🎯 简化调试:只在边界chunk时记录索引映射
if (ReceptionConfig.DEBUG_CONFIG.ENABLE_CHUNK_LOGGING && (absoluteChunkIndex <= 2 || relativeChunkIndex <= 2)) {
postLogToBackend(
`[DEBUG-CHUNKS] Index mapping - absolute:${absoluteChunkIndex}, start:${startChunkIndex}, relative:${relativeChunkIndex}`
`[INDEX-MAP] abs:${absoluteChunkIndex}, start:${startChunkIndex}, rel:${relativeChunkIndex}`
);
}
@@ -195,10 +196,11 @@ export class ChunkProcessor {
const startChunkIndex = ReceptionConfig.getChunkIndexFromOffset(initialOffset);
const calculatedExpected = chunkMeta.totalChunks - startChunkIndex;
// 🎯 简化日志:只在数量不匹配时记录关键信息
if (chunkMeta.totalChunks !== expectedChunksCount && calculatedExpected !== expectedChunksCount) {
if (ReceptionConfig.DEBUG_CONFIG.ENABLE_CHUNK_LOGGING) {
postLogToBackend(
`[DEBUG-CHUNKS] Chunk count info - fileTotal: ${chunkMeta.totalChunks}, currentExpected: ${expectedChunksCount}, calculatedExpected: ${calculatedExpected}, startChunk: ${startChunkIndex}`
`[CHUNK-COUNT-MISMATCH] fileTotal:${chunkMeta.totalChunks}, expected:${expectedChunksCount}, calculated:${calculatedExpected}`
);
}
}
@@ -231,14 +233,17 @@ export class ChunkProcessor {
return;
}
// 🎯 简化日志:只记录边界chunk和异常情况
const { chunkMeta, absoluteChunkIndex, relativeChunkIndex } = result;
const lastFewChunks = relativeChunkIndex >= expectedChunksCount - 5;
const isFirstFew = absoluteChunkIndex <= 3;
const isLastFew = relativeChunkIndex >= expectedChunksCount - 3;
const hasIndexMismatch = writerExpectedIndex !== undefined && relativeChunkIndex !== writerExpectedIndex;
if (absoluteChunkIndex <= 970 || lastFewChunks) {
if (isFirstFew || isLastFew || hasIndexMismatch) {
postLogToBackend(
`[DEBUG-CHUNKS] 📦 Chunk #${absoluteChunkIndex} received - relative:${relativeChunkIndex}, size:${chunkMeta.chunkSize}${
writerExpectedIndex !== undefined ? `, writerExpects:${writerExpectedIndex}` : ''
}, isLastFew:${lastFewChunks}`
`[CHUNK-DETAIL] #${absoluteChunkIndex} rel:${relativeChunkIndex}${
hasIndexMismatch ? ` MISMATCH(expected:${writerExpectedIndex})` : ''
} size:${chunkMeta.chunkSize}`
);
}
}
@@ -304,39 +309,20 @@ export class ChunkProcessor {
const { sequencedCount, expectedChunksCount, currentTotalSize, expectedSize, isDataComplete } = stats;
// Only log at key moments to reduce noise
if (
isDataComplete ||
sequencedCount % ReceptionConfig.DEBUG_CONFIG.PROGRESS_LOG_INTERVAL === 0 ||
sequencedCount > expectedChunksCount - 10
) {
// Check last few chunks status
const lastChunkIndex = expectedChunksCount - 1;
const lastFewChunks = [];
// 🎯 关键日志3:只在完成时打印最终检查结果
if (isDataComplete) {
const startChunkIndex = ReceptionConfig.getChunkIndexFromOffset(initialOffset);
for (let i = Math.max(0, lastChunkIndex - 3); i <= lastChunkIndex; i++) {
const chunk = chunks[i];
const exists = chunk instanceof ArrayBuffer;
const size = exists ? (chunk as ArrayBuffer).byteLength : 0;
const absoluteIndex = startChunkIndex + i;
lastFewChunks.push(`rel#${i}(abs#${absoluteIndex}):${exists}(${size})`);
const missingChunks = [];
for (let i = 0; i < expectedChunksCount; i++) {
if (!chunks[i]) {
const absoluteIndex = startChunkIndex + i;
missingChunks.push(absoluteIndex);
}
}
postLogToBackend(`[DEBUG-COMPLETE] Check completion - file:${fileName}`);
postLogToBackend(
`[DEBUG-COMPLETE] Chunks: received:${sequencedCount}/${expectedChunksCount}, isSequenceComplete:${stats.sequencedCount === expectedChunksCount}`
);
postLogToBackend(
`[DEBUG-COMPLETE] Size: current:${currentTotalSize}, expected:${expectedSize}, sizeComplete:${currentTotalSize >= expectedSize}, diff:${
expectedSize - currentTotalSize
}`
);
postLogToBackend(
`[DEBUG-COMPLETE] LastChunks: ${lastFewChunks.join(", ")}`
);
postLogToBackend(
`[DEBUG-COMPLETE] IsDataComplete: ${isDataComplete}`
`[FINAL-CHECK] File: ${fileName}, received: ${sequencedCount}/${expectedChunksCount}, sizeDiff: ${expectedSize - currentTotalSize}, missing: [${missingChunks.join(',')}]`
);
}
}
@@ -7,6 +7,7 @@ import { StreamingFileWriter, SequencedDiskWriter } from "./StreamingFileWriter"
import { FileAssembler } from "./FileAssembler";
import { ProgressReporter, ProgressCallback } from "./ProgressReporter";
import { ReceptionConfig } from "./ReceptionConfig";
import { ChunkRangeCalculator } from "@/lib/utils/ChunkRangeCalculator";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NODE_ENV;
@@ -135,19 +136,15 @@ export class FileReceiveOrchestrator implements MessageProcessorDelegate {
);
if (ReceptionConfig.DEBUG_CONFIG.ENABLE_CHUNK_LOGGING) {
const totalChunks = ReceptionConfig.calculateTotalChunks(fileInfo.size);
const startChunkIndex = ReceptionConfig.getChunkIndexFromOffset(offset);
postLogToBackend(`[DEBUG-CHUNKS] File: ${fileInfo.name}`);
postLogToBackend(
`[DEBUG-CHUNKS] File size: ${fileInfo.size}, offset: ${offset}`
// 🎯 关键日志2:接收端总结信息 - 使用统一的chunk范围计算逻辑
const chunkRange = ChunkRangeCalculator.getChunkRange(
fileInfo.size,
offset,
ReceptionConfig.FILE_CONFIG.CHUNK_SIZE
);
postLogToBackend(
`[DEBUG-CHUNKS] Total chunks in file: ${totalChunks} (0-${totalChunks - 1})`
);
postLogToBackend(`[DEBUG-CHUNKS] Start chunk index: ${startChunkIndex}`);
postLogToBackend(
`[DEBUG-CHUNKS] Expected chunks: ${expectedChunksCount}`
`[RECV-SUMMARY] File: ${fileInfo.name}, expected: ${expectedChunksCount}, calculated: ${chunkRange.totalChunks}, startChunk: ${chunkRange.startChunk}, endChunk: ${chunkRange.endChunk}, absoluteTotal: ${chunkRange.absoluteTotalChunks}`
);
}
+39 -60
View File
@@ -1,5 +1,6 @@
import { CustomFile } from "@/types/webrtc";
import { TransferConfig } from "./TransferConfig";
import { ChunkRangeCalculator } from "@/lib/utils/ChunkRangeCalculator";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NODE_ENV;
/**
@@ -51,19 +52,14 @@ export class StreamingFileReader {
this.fileReader = new FileReader();
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 📖 StreamingFileReader created - file: ${file.name}, size: ${(
this.totalFileSize /
1024 /
1024
).toFixed(1)}MB`
);
// 🔍 调试续传初始化
const expectedGlobalChunk = Math.floor(
startOffset / this.NETWORK_CHUNK_SIZE
// 🎯 关键日志1:发送端总结信息 - 使用统一的chunk范围计算逻辑
const chunkRange = ChunkRangeCalculator.getChunkRange(
this.totalFileSize,
startOffset,
this.NETWORK_CHUNK_SIZE
);
postLogToBackend(
`[DEBUG-RESUME] 🏗️ StreamingFileReader created - totalFileOffset:${this.totalFileOffset}, currentBatchStartOffset:${this.currentBatchStartOffset}, expectedGlobalChunk:${expectedGlobalChunk}`
`[SEND-SUMMARY] File: ${file.name}, offset: ${startOffset}, startChunk: ${chunkRange.startChunk}, endChunk: ${chunkRange.endChunk}, willSend: ${chunkRange.totalChunks}, absoluteTotal: ${chunkRange.absoluteTotalChunks}`
);
}
}
@@ -96,15 +92,22 @@ export class StreamingFileReader {
// 4. Update state
this.updateChunkState(networkChunk);
// Delete frequent chunk progress logs
// 🔍 调试chunk发送 (前5个和最后5个chunks)
const totalChunks = this.calculateTotalNetworkChunks();
const isLastFew = globalChunkIndex >= (totalChunks - 5);
if (developmentEnv === "development" && (globalChunkIndex <= 5 || isLastFew || isLast)) {
postLogToBackend(
`[DEBUG-CHUNKS] 📤 Send chunk #${globalChunkIndex}/${totalChunks} - size:${networkChunk.byteLength}, isLast:${isLast}, fileOffset:${this.totalFileOffset - networkChunk.byteLength}`
// 🎯 关键日志:边界chunk验证(临时保留用于验证修复效果)
if (developmentEnv === "development") {
const totalChunks = this.calculateTotalNetworkChunks();
const currentOffset = this.totalFileOffset - networkChunk.byteLength;
const firstChunkIndex = Math.floor(
currentOffset / this.NETWORK_CHUNK_SIZE
);
const isFirst =
globalChunkIndex === firstChunkIndex ||
(currentOffset === 0 && globalChunkIndex === 0);
if (isFirst || isLast) {
postLogToBackend(
`[BOUNDARY] Chunk #${globalChunkIndex}/${totalChunks}, isFirst: ${isFirst}, isLast: ${isLast}, size: ${networkChunk.byteLength}`
);
}
}
return {
@@ -181,35 +184,24 @@ export class StreamingFileReader {
const batchStartOffset = this.totalFileOffset;
this.currentBatchStartOffset = batchStartOffset;
// 🔧 修复:如果不是从batch边界开始,说明是续传情况,需要计算正确的batch内索引
if (batchStartOffset % this.BATCH_SIZE !== 0) {
// 续传情况:不是从batch边界开始
const globalChunkIndex = Math.floor(
batchStartOffset / this.NETWORK_CHUNK_SIZE
);
this.currentChunkIndexInBatch =
globalChunkIndex % this.CHUNKS_PER_BATCH;
} else {
// 正常情况:从batch边界开始
this.currentChunkIndexInBatch = 0;
}
// 🔧 修复:简化batch内索引计算逻辑
// 由于calculateGlobalChunkIndex现在直接基于totalFileOffset计算,
// batch内索引只需要基于当前batch的起始位置计算即可
const chunkOffsetInBatch =
batchStartOffset -
Math.floor(batchStartOffset / this.BATCH_SIZE) * this.BATCH_SIZE;
this.currentChunkIndexInBatch = Math.floor(
chunkOffsetInBatch / this.NETWORK_CHUNK_SIZE
);
// Only output batch reading logs in development environment
if (developmentEnv === "development") {
// Only output essential batch reading logs in development environment
if (developmentEnv === "development" && batchSize > this.BATCH_SIZE / 2) {
const totalTime = performance.now() - startTime;
const speedMBps = batchSize / 1024 / 1024 / (totalTime / 1000);
postLogToBackend(
`[DEBUG] 📖 BATCH_READ - size: ${(batchSize / 1024 / 1024).toFixed(
`[BATCH-READ] 📖 size: ${(batchSize / 1024 / 1024).toFixed(
1
)}MB, time: ${totalTime.toFixed(0)}ms, speed: ${speedMBps.toFixed(
1
)}MB/s`
);
// 🔍 调试batch内索引设置
postLogToBackend(
`[DEBUG-RESUME] 📖 BATCH loaded - batchStartOffset:${batchStartOffset}, currentChunkIndexInBatch:${
this.currentChunkIndexInBatch
}, isResume:${batchStartOffset % this.BATCH_SIZE !== 0}`
)}MB, speed: ${speedMBps.toFixed(1)}MB/s`
);
}
} catch (error) {
@@ -278,25 +270,12 @@ export class StreamingFileReader {
/**
* 📊 Calculate global network chunk index
* 🔧 Simplified logic: directly calculate based on file offset to avoid batch boundary errors
*/
private calculateGlobalChunkIndex(): number {
const batchesBefore = Math.floor(
this.currentBatchStartOffset / this.BATCH_SIZE
);
const chunksInPreviousBatches = batchesBefore * this.CHUNKS_PER_BATCH;
const result = chunksInPreviousBatches + this.currentChunkIndexInBatch;
// 🔍 调试chunk索引计算
if (
developmentEnv === "development" &&
this.currentChunkIndexInBatch <= 5
) {
postLogToBackend(
`[DEBUG-RESUME] 🧮 calculateGlobalChunkIndex - batchStartOffset:${this.currentBatchStartOffset}, batchesBefore:${batchesBefore}, chunksInPrev:${chunksInPreviousBatches}, chunkInBatch:${this.currentChunkIndexInBatch}, result:${result}`
);
}
return result;
// 🎯 核心修复:直接基于当前文件偏移量计算chunk索引,避免复杂的batch计算
// 这确保了与接收端ReceptionConfig.getChunkIndexFromOffset()完全一致的计算逻辑
return Math.floor(this.totalFileOffset / this.NETWORK_CHUNK_SIZE);
}
/**
@@ -0,0 +1,82 @@
/**
* 🚀 Chunk range calculation utilities
* Provides unified chunk calculation logic to ensure consistency between sender and receiver
*/
export class ChunkRangeCalculator {
/**
* Calculate chunk range for a file with given parameters
* This method ensures both sender and receiver use identical calculation logic
*/
static getChunkRange(fileSize: number, startOffset: number, chunkSize: number) {
// Calculate starting chunk index
const startChunk = Math.floor(startOffset / chunkSize);
// Calculate ending chunk index based on the last byte of the file
const lastByteIndex = fileSize - 1;
const endChunk = Math.floor(lastByteIndex / chunkSize);
// Calculate total chunks to be sent/received (from startChunk to endChunk inclusive)
const totalChunks = endChunk - startChunk + 1;
// Calculate absolute total chunks in the entire file
const absoluteTotalChunks = Math.ceil(fileSize / chunkSize);
return {
startChunk, // First chunk index to process
endChunk, // Last chunk index to process
totalChunks, // Number of chunks to process (for resume transfers)
absoluteTotalChunks // Total chunks in the entire file
};
}
/**
* Calculate expected chunks count for resume transfer
* Identical to ReceptionConfig.calculateExpectedChunks()
*/
static calculateExpectedChunks(fileSize: number, startOffset: number, chunkSize: number): number {
return Math.ceil((fileSize - startOffset) / chunkSize);
}
/**
* Get chunk index from file offset
* Identical to ReceptionConfig.getChunkIndexFromOffset()
*/
static getChunkIndexFromOffset(offset: number, chunkSize: number): number {
return Math.floor(offset / chunkSize);
}
/**
* Get file offset from chunk index
* Identical to ReceptionConfig.getOffsetFromChunkIndex()
*/
static getOffsetFromChunkIndex(chunkIndex: number, chunkSize: number): number {
return chunkIndex * chunkSize;
}
/**
* Validate chunk index within expected range
*/
static isChunkIndexValid(
chunkIndex: number,
startOffset: number,
fileSize: number,
chunkSize: number
): boolean {
const range = this.getChunkRange(fileSize, startOffset, chunkSize);
return chunkIndex >= range.startChunk && chunkIndex <= range.endChunk;
}
/**
* Calculate relative chunk index from absolute chunk index
* Used by receiver to map sender's absolute index to local array index
*/
static getRelativeChunkIndex(
absoluteChunkIndex: number,
startOffset: number,
chunkSize: number
): number {
const startChunkIndex = this.getChunkIndexFromOffset(startOffset, chunkSize);
return absoluteChunkIndex - startChunkIndex;
}
}