1,背景描述
- 在使用flutter中的archive压缩依赖包开发一个名叫*超级密码*的全平台工具,针对加密的zip进行解压时出现如下错误,排查一波最后解决
复制代码 2,错误现象:
解压文件只能解压部门文件,其他的没有解压出来
报错日志:
- Failed to send request: {"jsonrpc":"2.0","id":"148","method":"lookupResolvedPackageUris","params":{"isolateId":"isolates/5812741238645739","uris":["package:archive/src/io/extract_archive_to_disk.dart"],"local":true}}
- Failed to send request: {"jsonrpc":"2.0","id":"147","method":"lookupResolvedPackageUris","params":{"isolateId":"isolates/5812741238645739","uris":["package:archive/src/io/extract_archive_to_disk.dart"],"local":true}}
- Failed to send request: {"jsonrpc":"2.0","id":"146","method":"getStack","params":{"isolateId":"isolates/5812741238645739"}}
- Failed to send request: {"jsonrpc":"2.0","id":"149","method":"invoke","params":{"isolateId":"isolates/5812741238645739","targetId":"objects/227/0","selector":"toString","argumentIds":[],"disableBreakpoints":true}}
- Failed to send request: {"jsonrpc":"2.0","id":"150","method":"invoke","params":{"isolateId":"isolates/5812741238645739","targetId":"objects/229/0","selector":"toString","argumentIds":[],"disableBreakpoints":true}}
- Failed to send request: {"jsonrpc":"2.0","id":"151","method":"invoke","params":{"isolateId":"isolates/5812741238645739","targetId":"objects/230/0","selector":"toString","argumentIds":[],"disableBreakpoints":true}}
复制代码 3,相干代码:
- Future<void> extractFileToDisk(String inputPath, String outputPath,
- {String? password, int? bufferSize, ArchiveCallback? callback}) async {
- Directory? tempDir;
- var archivePath = inputPath;
- var posixSupported = posix.isPosixSupported();
- if (inputPath.endsWith('tar.gz') || inputPath.endsWith('tgz')) {
- tempDir = Directory.systemTemp.createTempSync('dart_archive');
- archivePath = path.join(tempDir.path, 'temp.tar');
- final input = InputFileStream(inputPath);
- final output = OutputFileStream(archivePath, bufferSize: bufferSize);
- GZipDecoder().decodeStream(input, output);
- await input.close();
- await output.close();
- } else if (inputPath.endsWith('tar.bz2') || inputPath.endsWith('tbz')) {
- tempDir = Directory.systemTemp.createTempSync('dart_archive');
- archivePath = path.join(tempDir.path, 'temp.tar');
- final input = InputFileStream(inputPath);
- final output = OutputFileStream(archivePath, bufferSize: bufferSize);
- BZip2Decoder().decodeStream(input, output);
- await input.close();
- await output.close();
- } else if (inputPath.endsWith('tar.xz') || inputPath.endsWith('txz')) {
- tempDir = Directory.systemTemp.createTempSync('dart_archive');
- archivePath = path.join(tempDir.path, 'temp.tar');
- final input = InputFileStream(inputPath);
- final output = OutputFileStream(archivePath, bufferSize: bufferSize);
- XZDecoder().decodeStream(input, output);
- await input.close();
- await output.close();
- }
- InputStream? toClose;
- Archive archive;
- if (archivePath.endsWith('tar')) {
- final input = InputFileStream(archivePath);
- archive = TarDecoder().decodeStream(input, callback: callback);
- toClose = input;
- } else if (archivePath.endsWith('zip')) {
- final input = InputFileStream(archivePath);
- archive = ZipDecoder()
- .decodeStream(input, password: password, callback: callback);
- toClose = input;
- } else {
- throw ArgumentError.value(inputPath, 'inputPath',
- 'Must end tar.gz, tgz, tar.bz2, tbz, tar.xz, txz, tar or zip.');
- }
- for (final file in archive) {
- final filePath = path.join(outputPath, path.normalize(file.name));
- if (!_isWithinOutputPath(outputPath, filePath)) {
- continue;
- }
- if (file.isSymbolicLink) {
- if (!_isValidSymLink(outputPath, file)) {
- continue;
- }
- }
- if (file.isDirectory && !file.isSymbolicLink) {
- Directory(filePath).createSync(recursive: true);
- continue;
- }
- if (file.isSymbolicLink) {
- final link = Link(filePath);
- final p = path.normalize(file.symbolicLink ?? "");
- link.createSync(p, recursive: true);
- } else if (file.isFile) {
- final output = OutputFileStream(filePath, bufferSize: bufferSize);
- try {
- file.writeContent(output);
- } catch (_) {}
- if (posixSupported) {
- posix.chmod(filePath, file.unixPermissions.toRadixString(8));
- }
- await output.close();
- }
- }
复制代码 4,代码逻辑&问题排查
1,archive已经解析出来
2,目录正常创建
3,第一个文件正常创建
4,第二个文件创建失败
- 进行步进debug发现,第二个文件进入循环就直接出错了!!!
- 定位错误起始点
- plugins/archive/lib/src/io/extract_archive_to_disk.dart:227
- await output.close();
复制代码 5,分析问题
5.1,先锤上一拳试试!
直接解释掉这个close部门的代码,由于在java中,这些资源不关闭一般只会浪费资源,不会堕落
解释掉后,代码运行正常!我人傻了!
5.2,为什么会有影响?
理论上final output = OutputFileStream(filePath, bufferSize: bufferSize);是相互独立的才对,会什么会有影响呢?
问一下gpt
- 问题原因分析
- 在这段代码中,如果解开 // await output.close(); 的注释,会导致文件只能解压出一个文件,第二个文件无法写入。这是因为 await output.close(); 是一个异步操作,它会阻塞当前的执行流程,直到文件流关闭完成。在异步操作完成之前,代码不会继续执行后续的循环迭代,这就导致后续的文件无法被处理。
- 具体来说,当第一个文件的 output.close() 被调用时,代码会等待这个操作完成,而在等待的过程中,整个 for 循环会被暂停,无法处理下一个文件,从而导致只有第一个文件能被正确解压。
- 修复方案
- 为了避免这个问题,我们需要确保每个文件的写入和关闭操作都是独立的,不会影响到后续文件的处理。可以将文件的写入和关闭操作封装到一个异步函数中,然后在 for 循环中使用 await 关键字来等待每个文件的处理完成,这样可以确保每个文件的处理都是顺序进行的,不会相互干扰。
- 嗯。。。。。,感觉说的有问题。阻塞下一次for循环,为什么会“中断”for循环?
复制代码 以下是修复后的代码:
- for (final file in archive) {
- final filePath = path.join(outputPath, path.normalize(file.name));
- if (!_isWithinOutputPath(outputPath, filePath)) {
- continue;
- }
- if (file.isSymbolicLink) {
- if (!_isValidSymLink(outputPath, file)) {
- continue;
- }
- }
- if (file.isDirectory && !file.isSymbolicLink) {
- Directory(filePath).createSync(recursive: true);
- continue;
- }
- if (file.isSymbolicLink) {
- final link = Link(filePath);
- final p = path.normalize(file.symbolicLink ?? "");
- link.createSync(p, recursive: true);
- } else if (file.isFile) {
- // 封装文件写入和关闭操作的异步函数
- await _writeFile(file, filePath, bufferSize, posixSupported);
- }
- }
- // 封装文件写入和关闭操作的异步函数
- Future<void> _writeFile(ArchiveFile file, String filePath, int? bufferSize, bool posixSupported) async {
- final output = OutputFileStream(filePath, bufferSize: bufferSize);
- try {
- file.writeContent(output);
- } catch (_) {}
- if (posixSupported) {
- posix.chmod(filePath, file.unixPermissions.toRadixString(8));
- }
- // 关闭文件流
- await output.close();
- }
复制代码 我尝试了一下无效!还是错误的
5.3,思考如何解决呢
方案一:直接解释掉
查一下不要这个在dart中会有什么问题
- 将 await output.close(); 注释掉可能会引发以下几个问题:
- 1. 数据丢失或不完整
- 缓冲区数据未完全写入:
- OutputFileStream 通常会使用缓冲区来提高写入性能。当你调用 writeContent 方法写入数据时,数据可能先被存储在缓冲区中,而不是立即写入到磁盘文件。close 方法的一个重要作用是将缓冲区中的剩余数据刷新到磁盘。如果不调用 close 方法,缓冲区中的数据可能不会被完全写入磁盘,导致文件内容不完整。
- 示例:
- 假设 OutputFileStream 的缓冲区大小为 4KB,而你写入的数据量为 4.5KB。前 4KB 数据会被存储在缓冲区中,当你继续写入 0.5KB 数据时,可能会触发缓冲区的部分刷新,但最后一部分数据可能仍留在缓冲区中。如果不调用 close 方法,这部分数据就不会被写入磁盘。
- 2. 资源泄漏
- 文件描述符未释放:
- 在操作系统层面,每个打开的文件都会占用一个文件描述符(file descriptor)。文件描述符是一种有限的系统资源。当你打开一个文件(通过 OutputFileStream)时,操作系统会分配一个文件描述符给该文件。如果不调用 close 方法,文件描述符将不会被释放,随着打开文件数量的增加,可能会耗尽系统的文件描述符资源,导致后续文件无法正常打开。
- 示例:
- 在一个长时间运行的程序中,如果不断地打开文件进行写入操作而不关闭它们,最终可能会出现 Too many open files 错误,因为系统的文件描述符资源已经被耗尽。
- 3. 数据不一致
- 文件状态未正确更新:
- 关闭文件时,操作系统会更新文件的元数据,如文件大小、修改时间等。如果不调用 close 方法,这些元数据可能不会被正确更新,导致文件状态不一致。例如,文件的实际大小可能与文件系统中记录的大小不一致。
- 示例:
- 当你使用 ls -l 命令查看文件信息时,文件的大小可能显示不正确,因为文件的实际大小在文件关闭时才会被正确记录。
- 4. 后续操作可能失败
- 其他程序无法访问文件:
- 当一个文件被打开且未关闭时,其他程序可能无法对该文件进行读取或写入操作。这是因为操作系统会对打开的文件进行锁定,以确保数据的一致性。如果不调用 close 方法,其他程序可能会因为无法获取文件锁而无法访问该文件。
- 示例:
- 如果你在程序中打开一个文件进行写入操作,并且没有关闭它,然后尝试在另一个程序中读取该文件,可能会收到 File is in use 错误。
- 综上所述,虽然注释掉 await output.close(); 可能在某些情况下看起来程序可以正常运行,但实际上存在潜在的风险,可能会导致数据丢失、资源泄漏、数据不一致以及后续操作失败等问题。因此,建议在完成文件写入操作后,始终调用 close 方法来确保文件的正确关闭。
复制代码 方案二:去掉同步等待
去掉同步等待后,output.close();不会壅闭for循环的进行
那没问题了,GG
另外,我自己是javaer,对于flutter还是个新手,有大牛知道此中本质的原因,可以给我留言解答,阿里嘎多
6,解决方案
- // await output.close();
- // 直接去掉await等待 修改为如下部分
- output.close();
复制代码 7,其他
赞助或者有偿解决技术问题,可以接洽邮箱 linkeetop@foxmail.com
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |