brendan-duncan / archive

Dart library to encode and decode various archive and compression formats, such as Zip, Tar, GZip, ZLib, and BZip2.
MIT License
395 stars 130 forks source link

Out of Memory on iphone6P #295

Open luyun181 opened 8 months ago

luyun181 commented 8 months ago

2023-11-06 17:27:03.618865+0800 Runner[3369:65921] [VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: Out of Memory

0 _ensureFastAndSerializableByteData (dart:io/common.dart:129:23)

1 _FilterSink.addSlice (dart:io/data_transformer.dart:500:11)

2 _FilterSink.add (dart:io/data_transformer.dart:489:5)

3 ZLibDecoder.convert (dart:io/data_transformer.dart:363:9)

4 inflateBuffer_ (package:archive/src/zlib/_inflate_buffer_io.dart:4:33)

5 inflateBuffer (package:archive/src/zlib/inflate_buffer.dart:6:10)

6 ZipFile.content (package:archive/src/zip/zip_file.dart:159:22)

7 ZipDecoder.decodeBuffer (package:archive/src/zip_decoder.dart:35:41)

8 ZipDecoder.decodeBytes (package:archive/src/zip_decoder.dart:16:12)

9 FileHandle.unpackBlockZip (package:grid_assistant_flutter_object/utils/file_handle.dart:71:34)

10 _RootZone.runUnary (dart:async/zone.dart:1661:54)

11 _FutureListener.handleValue (dart:async/future_impl.dart:147:18)

12 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:784:45)

13 Future._propagateToListeners (dart:async/future_impl.dart:813:13)

14 Future._completeWithValue (dart:async/future_impl.dart:584:5)

15 Future._asyncCompleteWithValue. (dart:async/future_impl.dart:657:7)

16 _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)

luyun181 commented 8 months ago

Exhausted heap space, trying to allocate 2466736 bytes.

blakeyblake commented 8 months ago

I am having the same problem in android TV emulation API 26 when trying to unzip an archive containing a 300MB mp4 file. We are using 3.4.9 of the package. We have tried this using the following code:

    var archive = ZipDecoder().decodeBytes(bytes
      //, password: 'password'
    );
    for (var file in archive) {
      var fileName = '$dir${file.name}';
      print("fileName $fileName");
      if (file.isFile && !fileName.contains("__MACOSX")) {
        var outFile = File(fileName);
        var maxSize = file.size;
        outFile = await outFile.create(recursive: true);
        while (outFile.lengthSync() < maxSize) {
          List<int> content;
          var lengthSync = outFile.lengthSync();
          if (lengthSync + 100000 < maxSize) {
            content = file.content.sublist(lengthSync, lengthSync + 100000);
          } else {
            content = file.content.sublist(lengthSync, maxSize);
          }
          await outFile.writeAsBytes(content, mode: FileMode.append);
        }
      }
    }
    archive.clear();

We also tried using archive_io but this just crashes the app entirely using:

var archive = ZipDecoder().decodeBuffer(inputStream
  //, password: 'password'
);
await extractArchiveToDiskAsync(archive, dir);
archive.clear();

finally we tried with streams as well using which crashed at the same mp4 file:

    final inputStream = InputFileStream(zippedFile.path);
    // Decode the zip from the InputFileStream. The archive will have the contents of the
    // zip, without having stored the data in memory. 
    final archive = ZipDecoder().decodeBuffer(inputStream);
    // For all of the entries in the archive
    for (var file in archive.files) {
      var fileName = '$dir${file.name}';
      // If it's a file and not a directory 
      if (file.isFile) {
        if(globals.testMode)
        {
          print("fileName $fileName");
        }
        // Write the file content to a directory called 'out'.
        // In practice, you should make sure file.name doesn't include '..' paths
        // that would put it outside of the extraction directory.
        // An OutputFileStream will write the data to disk.
        final outputStream = OutputFileStream(fileName);
        // The writeContent method will decompress the file content directly to disk without
        // storing the decompressed data in memory. 
        file.writeContent(outputStream);
        // Make sure to close the output stream so the File is closed.
        outputStream.close();
      }
    }
    archive.clear();

Any additional guidance on how we can manage an archive with a large file inside to unzip would be appreciated.

brendan-duncan commented 7 months ago

You can try adding file.close() after file.writeContent(outputStream) to release the memory used by that file. That way only one file at a time will be decompressed in memory.

blakeyblake commented 7 months ago

We tried this and now when we reach the large file, the app crashes with a null pointer error using method 3 above. The same thing happens using method 2 above, same error. We are running this in an async function. When using the first method, We still get out of memory and that happens when we try to access 'file.content' on the large file in the archive.

We even tried this with buffers on the streams, but the same errors occured.

a-v-ebrahimi commented 4 months ago

Same problem here for 300mb file on Android 7 emulator.

Nico04 commented 1 month ago

Same issue on a Android 14 emulator, with a 15MB archive