SDWebImage: SDWebImage 5.17.0 iOS 16 SDImageIOAnimatedCoder createFrameAtIndex: still crashes EXC_BAD_ACCESS
New Issue Checklist
- I have read and understood the CONTRIBUTING guide
- I have read the Documentation
- I have searched for a similar issue in the project and found none
Issue Info
| Info | Value |
|---|---|
| Platform Name | iOS |
| Platform Version | 16.5.1 |
| SDWebImage Version | 5.17.0 |
| Integration Method | SwiftPM |
| Xcode Version | Xcode 15 |
| Repro rate | sometimes (found in Xcode Organizer crashlogs) |
| Repro with our demo prj | sorry haven’t tried |
| Demo project link | - |
Issue Description and Steps
Hi we have an iOS app that uses SDWebImage for animated images. We use SDAnimatedImageView + SDAnimatedImage to display. Project is written in Swift and uses a mix of SwiftUI and UIKit. For loading, .scaleDownLargeImages is always passed. But context option “SDImageCoderDecodeUseLazyDecoding” is not passed. I find that setting SDImageCoderDecodeUseLazyDecoding = true causes noticeable lag when scrolling, so I prefer to not set it to true. However, in Xcode Organizer, we see several crash logs like so:
0 libsystem_platform.dylib 0x000000020a0fbe14 _platform_memmove + 84
1 ImageIO 0x00000001c51591e0 GIFBufferInfo::GIFBufferInfo(unsigned char*, bool, unsigned int, unsigned int, unsigned int) + 88 (GIF_common.cpp:27)
2 ImageIO 0x00000001c5169f50 std::__1::__shared_ptr_emplace<GIFBufferInfo, std::__1::allocator<GIFBufferInfo> >::__shared_ptr_emplace[abi:v15006]<unsigned char*&, bool, unsigned int&, unsigned int&, unsigned int>(std::__1::all... + 56 (shared_ptr.h:294)
3 ImageIO 0x00000001c5169ee4 std::__1::shared_ptr<GIFBufferInfo> std::__1::allocate_shared[abi:v15006]<GIFBufferInfo, std::__1::allocator<GIFBufferInfo>, unsigned char*&, bool, unsigned int&, unsigned int&, unsigned int, void>... + 84 (shared_ptr.h:953)
4 ImageIO 0x00000001c51696c0 GIFReadPlugin::copyImageBlockSet(InfoRec*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 872 (GIF_readPlugin.cpp:1520)
5 ImageIO 0x00000001c4fa4b70 IIO_Reader::CopyImageBlockSetProc(void*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 228 (IIOReader.cpp:1454)
6 ImageIO 0x00000001c4fa08d4 IIOImageProviderInfo::copyImageBlockSetWithOptions(CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 740 (CGImagePlus.cpp:2326)
7 ImageIO 0x00000001c4fa8430 IIOImageProviderInfo::CopyImageBlockSetWithOptions(void*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*) + 840 (CGImagePlus.cpp:2638)
8 CoreGraphics 0x00000001c1caafe0 imageProvider_retain_data + 88 (CGDataProviderImageProvider.c:91)
9 CoreGraphics 0x00000001c1cc8530 CGDataProviderRetainData + 84 (CGDataProvider.c:922)
10 CoreGraphics 0x00000001c1ce4b98 provider_for_destination_retain_data + 24 (CGDataProviderForDestination.c:705)
11 CoreGraphics 0x00000001c1cc8530 CGDataProviderRetainData + 84 (CGDataProvider.c:922)
12 CoreGraphics 0x00000001c1ca7ed8 CGAccessSessionCreate + 104 (CGAccessSession.c:71)
13 CoreGraphics 0x00000001c1ca28d0 get_access_session + 48 (CGSImage.c:488)
14 CoreGraphics 0x00000001c1cc9bf4 img_raw_read + 944 (CGSImage.c:627)
15 CoreGraphics 0x00000001c1cb841c img_interpolate_read + 588 (CGSImage.c:2547)
16 CoreGraphics 0x00000001c1c76a80 img_data_lock + 8748 (CGSImage.c:5553)
17 CoreGraphics 0x00000001c1cb99ec CGSImageDataLock + 1324 (CGSImage.c:5847)
18 CoreGraphics 0x00000001c1c8d12c ripc_AcquireRIPImageData + 708 (RIPImage.c:337)
19 CoreGraphics 0x00000001c1caa230 ripc_DrawImage + 820 (RIPContext.c:1432)
20 CoreGraphics 0x00000001c1c811dc CGContextDrawImageWithOptions + 1112 (CGContextImage.c:303)
21 ImageIO 0x00000001c4fbfff0 CGImageCreateCopyWithParametersNew(CGImage*, CGColor*, CGAffineTransform, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, CGColorSpace*, unsigned int, bool, CGColorRender... + 1640 (CGImageCopy.cpp:784)
22 ImageIO 0x00000001c4fac560 IIOImageSource::createThumbnailAtIndex(unsigned long, IIODictionary*) + 2692 (CGImageSource.cpp:2192)
23 ImageIO 0x00000001c4f98844 CGImageSourceCreateThumbnailAtIndex + 388 (CGImageSource.cpp:4269)
24 <App Name> 0x0000000102973428 +[SDImageIOAnimatedCoder createFrameAtIndex:source:scale:preserveAspectRatio:thumbnailSize:lazyDecode:animatedImage:] + 820 (SDImageIOAnimatedCoder.m:269)
25 <App Name> 0x0000000102973a9c -[SDImageIOAnimatedCoder decodedImageWithData:options:] + 1032 (SDImageIOAnimatedCoder.m:449)
26 <App Name> 0x0000000102972004 -[SDImageCodersManager decodedImageWithData:options:] + 276 (SDImageCodersManager.m:109)
27 <App Name> 0x000000010296ce4c SDImageCacheDecodeImageData + 396 (SDImageCacheDefine.m:123)
28 <App Name> 0x000000010296a2ac -[SDImageCache diskImageForKey:data:options:context:] + 112 (SDImageCache.m:524)
29 <App Name> 0x000000010296abe0 __73-[SDImageCache queryCacheOperationForKey:options:context:cacheType:done:]_block_invoke.161 + 420 (SDImageCache.m:677)
30 <App Name> 0x000000010296ae74 __73-[SDImageCache queryCacheOperationForKey:options:context:cacheType:done:]_block_invoke.164 + 68 (SDImageCache.m:701)
There are not many threads created, only 9 threads are found in the crash log. This crash is EXC_BAD_ACCESS (SIGSEGV), and after looking around for similar issues, this looks like some kind of out of memory crash? My question is what is the best approach right now to avoid this kind of problem, besides trying SDImageCoderDecodeUseLazyDecoding = true ? We are using a fairly new version of SDWebImage as well (5.17.0), and I see several issues in the past attempting to address or fix similar crash issues, yet it still seems to happen. The images are loaded in either a SwiftUI List or UIKit UICollectionView, it is common for several animated images to appear together (but they are not the same image), these animated images are user uploaded, so we have no size guarantee. My guess is that some of the animated images are very big, causing out of memory when decoding. Is there some approach I can take to better limit the image size, in order to avoid crashes like this? Thanks in advance!
About this issue
- Original URL
- State: open
- Created 9 months ago
- Comments: 22 (12 by maintainers)
Yes, animted image = frame count * bytes per frame (= bytesPerPixel * pixel count)
Yes, but pay attention this is not global control but for current image view
Yes, then buffer is just a NSMutableDictionary hosted in SDAnimatedPlayer which retained by SDAnimatedImageView
Yes, a new setImage: call cause that SDAnimatedImagePlayer been dealloced, so as buffer NSMutableDictionary
Maybe. A OOM may not always been that case “total RAM usage is high”, a rapid memory peak may still cause OOM. This can help for that
Yes. Because I told that thumnail by defaults store full image data into disk cache(actually no thumnail data is stored into disk, only thumnail image object into memory). When you query without thumnail it will hit disk cache async less than 1 second
Maybe, but this limit is per-image-view-level, right ? If you have 100 image view (which means, you use SwiftUI.List/UIScrollView, or some non-lazy no re-using cell), you keep all image view into memory and consume RAM even that image view is not visible.
For this case, we have a special option called
clearBufferWhenStopped(defaults to NO, I doubt this is a wrong design), because when the image view is not visible, the image view will be sent astopAnimatingcall and get stopped. Then it will clear temp buffer.Another solution, it’s strongly recommaned to use the LazyVStack or UITableView/UICollectionView instead, which is lazy and cell-reusing. So actually even you have a list of 100 GIF, in the RAM there are only SDAnimatedImageView living, the temp buffer is guranteed to < 5 GIF images all frames
It will return thumbnail image, and nil data. But by defaults, the data is stored into disk cache, thumbnail image is stored into memory cache. This behavior can also be controlled by advanced context arg
.storeCacheType,.originalStoreCacheTypetwo, see documentation thereFrom newer version, you can always pass decode options to coder and do customization, don’t need the same naming context arg (There are one
SDWebImageContextImageThumbnailPixelSize, same name)This design is better, since actually decoder is plugin, which means, not written by me or SDWebImage, it can written by you and provide new option wo don’t know. Pass that raw dictionary directly to decoder is correct design.
or
This only supports some pre-defined context option which passthrough to decoder options (but not extensible)