chore:Fix the issue where the breakpoint resume receiver is missing one chunk of data
This commit is contained in:
@@ -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}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user