swiftmemory-leaksapple-watchwidgetkitapple-watch-complication

Watch complication memory crash - EXC_RESOURCE


I'm struggling with complications bug. Memory sometimes starts growing rapidly and within 3-5 sec reaches 15 MB limit. The app crashes with the error:

Thread 1: EXC_RESOURCE (RESOURCE_TYPE_MEMORY: high watermark memory limit exceeded) (limit=15 MB)

Stack trace:

Thread 1 Queue : com.apple.main-thread (serial)
#0  0x507c8864 in lzfseDecode ()
#1  0x507c788c in lzfse_decode_buffer ()
#2  0x507c645c in compression_decode_buffer ()
#3  0x52200ff0 in Deepmap2DecodeDefault ()
#4  0x5221d184 in DecodeTiledImage ()
#5  0x521ea5a4 in vImageDeepmap2Decode ()
#6  0x41ea601c in __CUIUncompressDeepmap2ImageData_block_invoke ()
#7  0x41ea074c in CUIUncompressDeepmap2ImageData ()
#8  0x41ea4548 in -[_CSIRenditionBlockData expandCSIBitmapData:fromSlice:makeReadOnly:] ()
#9  0x41ea1d48 in __csiCompressImageProviderCopyImageBlockSetWithOptions ()
#10 0x2d7cdcd8 in IIOImagePixelDataProvider::getBytesImageProvider(void*, unsigned long) ()
#11 0x2d7dac6c in PNGWritePlugin::writePNG(IIOImagePixelDataProvider*, IIODictionary*) ()
#12 0x2d7e1d64 in PNGWritePlugin::writeAll() ()
#13 0x2d7da558 in PNGWritePlugin::WriteProc(void*, void*, void*, void*) ()
#14 0x2d7cd044 in IIOImageDestination::finalizeDestination() ()
#15 0x2d807770 in CGImageDestinationFinalize ()
#16 0x244cd564 in ___lldb_unnamed_symbol212136 ()
#17 0x244cd0ec in ___lldb_unnamed_symbol212135 ()
#18 0x244cebfc in ___lldb_unnamed_symbol212193 ()
#19 0x2402f64c in ___lldb_unnamed_symbol166857 ()
#20 0x2402c180 in ___lldb_unnamed_symbol166740 ()
#21 0x2402cdec in ___lldb_unnamed_symbol166744 ()
#22 0x2402ca94 in ___lldb_unnamed_symbol166742 ()
#23 0x244cc404 in ___lldb_unnamed_symbol212112 ()
#24 0x244cce08 in ___lldb_unnamed_symbol212134 ()
#25 0x23b3f110 in ___lldb_unnamed_symbol123404 ()
#26 0x241fed00 in ___lldb_unnamed_symbol183123 ()
#27 0x241fce70 in ___lldb_unnamed_symbol183102 ()
#28 0x241fcf54 in ___lldb_unnamed_symbol183102 ()
#29 0x241fcf54 in ___lldb_unnamed_symbol183102 ()
#30 0x241fcf54 in ___lldb_unnamed_symbol183102 ()
#31 0x241fcf54 in ___lldb_unnamed_symbol183102 ()
#32 0x23c0e600 in ___lldb_unnamed_symbol130598 ()
#33 0x23c10a20 in ___lldb_unnamed_symbol130761 ()
#34 0x242f05e4 in ___lldb_unnamed_symbol193041 ()
#35 0x23c0dbf0 in ___lldb_unnamed_symbol130593 ()
#36 0x23c0e888 in ___lldb_unnamed_symbol130605 ()
#37 0x23c0e7ac in ___lldb_unnamed_symbol130604 ()
#38 0x23c0e9e8 in ___lldb_unnamed_symbol130607 ()
#39 0x27b5dc48 in ___lldb_unnamed_symbol5926 ()
#40 0x27ae5124 in ___lldb_unnamed_symbol2483 ()
#41 0x27ae4558 in ___lldb_unnamed_symbol2482 ()
#42 0x27b9f844 in ___lldb_unnamed_symbol7756 ()
#43 0x26214138 in _dispatch_call_block_and_release ()
#44 0x262159ac in _dispatch_client_callout ()
#45 0x2622246c in _dispatch_main_queue_drain ()
#46 0x262220c4 in _dispatch_main_queue_callback_4CF ()
#47 0x1d0aede4 in __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ ()
#48 0x1d08274c in __CFRunLoopRun ()
#49 0x1d0c76bc in CFRunLoopRunSpecific ()
#50 0x1baf5d78 in -[NSRunLoop(NSRunLoop) runMode:beforeDate:] ()
#51 0x1bb2ec14 in -[NSRunLoop(NSRunLoop) run] ()
#52 0x50520c3c in _xpc_objc_main ()
#53 0x50522d64 in _xpc_main ()
#54 0x50522f24 in xpc_main ()
#55 0x1bb66208 in -[NSXPCListener resume] ()
#56 0x3351142c in -[_EXRunningExtension resume] ()
#57 0x335112dc in -[_EXRunningExtension startWithArguments:count:] ()
#58 0x3352e52c in EXExtensionMain ()
#59 0x1c2644b8 in NSExtensionMain ()
#60 0x4e69167c in start ()

Alternative stack trace (appears rarely):

#0  0x4f0c6014 in _platform_memmove ()
#1  0x4f21e6c0 in ___lldb_unnamed_symbol110 ()
#2  0x4f21ddd4 in ___lldb_unnamed_symbol105 ()
#3  0x4f21bc14 in ___lldb_unnamed_symbol102 ()
#4  0x4f21b390 in deflate ()
#5  0x2c34be8c in png_compress_IDAT ()
#6  0x2c34c3ec in png_write_find_filter ()
#7  0x2c431d58 in _cg_png_write_row_sized ()
#8  0x2c34ae5c in PNGWritePlugin::writePNG(IIOImagePixelDataProvider*, IIODictionary*) ()
#9  0x2c351d64 in PNGWritePlugin::writeAll() ()
#10 0x2c34a558 in PNGWritePlugin::WriteProc(void*, void*, void*, void*) ()
#11 0x2c33d044 in IIOImageDestination::finalizeDestination() ()
#12 0x2c377770 in CGImageDestinationFinalize ()
#13 0x2303d564 in ___lldb_unnamed_symbol212136 ()
#14 0x2303d0ec in ___lldb_unnamed_symbol212135 ()
#15 0x2303ebfc in ___lldb_unnamed_symbol212193 ()
#16 0x22b9f64c in ___lldb_unnamed_symbol166857 ()
#17 0x22b9c180 in ___lldb_unnamed_symbol166740 ()
#18 0x22b9cdec in ___lldb_unnamed_symbol166744 ()
#19 0x22b9ca94 in ___lldb_unnamed_symbol166742 ()
#20 0x2303c404 in ___lldb_unnamed_symbol212112 ()
#21 0x2303ce08 in ___lldb_unnamed_symbol212134 ()
#22 0x226af110 in ___lldb_unnamed_symbol123404 ()
#23 0x22d6ed00 in ___lldb_unnamed_symbol183123 ()
#24 0x22d6ce70 in ___lldb_unnamed_symbol183102 ()
#25 0x22d6cf54 in ___lldb_unnamed_symbol183102 ()
#26 0x22d6cf54 in ___lldb_unnamed_symbol183102 ()
#27 0x22d6cf54 in ___lldb_unnamed_symbol183102 ()
#28 0x22d6cf54 in ___lldb_unnamed_symbol183102 ()
#29 0x2277e600 in ___lldb_unnamed_symbol130598 ()
#30 0x22780a20 in ___lldb_unnamed_symbol130761 ()
#31 0x22e605e4 in ___lldb_unnamed_symbol193041 ()
#32 0x2277dbf0 in ___lldb_unnamed_symbol130593 ()
#33 0x2277e888 in ___lldb_unnamed_symbol130605 ()
#34 0x2277e7ac in ___lldb_unnamed_symbol130604 ()
#35 0x2277e9e8 in ___lldb_unnamed_symbol130607 ()
#36 0x266cdc48 in ___lldb_unnamed_symbol5926 ()
#37 0x26655124 in ___lldb_unnamed_symbol2483 ()
#38 0x26654558 in ___lldb_unnamed_symbol2482 ()
#39 0x2670f844 in ___lldb_unnamed_symbol7756 ()
#40 0x24d84138 in _dispatch_call_block_and_release ()
#41 0x24d859ac in _dispatch_client_callout ()
#42 0x24d9246c in _dispatch_main_queue_drain ()
#43 0x24d920c4 in _dispatch_main_queue_callback_4CF ()
#44 0x1bc1ede4 in __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ ()
#45 0x1bbf274c in __CFRunLoopRun ()
#46 0x1bc376bc in CFRunLoopRunSpecific ()
#47 0x1a665d78 in -[NSRunLoop(NSRunLoop) runMode:beforeDate:] ()
#48 0x1a69ec14 in -[NSRunLoop(NSRunLoop) run] ()
#49 0x4f090c3c in _xpc_objc_main ()
#50 0x4f092d64 in _xpc_main ()
#51 0x4f092f24 in xpc_main ()
#52 0x1a6d6208 in -[NSXPCListener resume] ()
#53 0x3208142c in -[_EXRunningExtension resume] ()
#54 0x320812dc in -[_EXRunningExtension startWithArguments:count:] ()
#55 0x3209e52c in EXExtensionMain ()
#56 0x1add44b8 in NSExtensionMain ()
#57 0x4d20167c in start ()

What have I tried.

First of all I noticed that the crash is PNG-related. I commented-out all images from the complication replacing them with Rectangle(). I also commented-out all custom fonts, just in case. No luck.

I tried to profile the app. Instruments works terrible with Apple Watch. Normally different complications in the app take 1.5-6.5 MB. But then Instruments complain that the device is disconnected. I was never able to check it the extension for memory leaks.

It's important to notice that bug occurs only on Apple Watch device, never in simulator. Reproduced on two devices:

Complications are written in SwiftUI and WidgetKit.

Update:

I added print into onAppear and onDisappear to every complication View. When I run the app every view prints onAppear right before the crash. Console output looks like:

ArcadeComplMoveView onAppear
ClassicComplBatteryView onAppear
DigitalComplRectView onAppear
DigitalComplRectView onAppear
UltraComplMoonphaseView onAppear
UltraComplMoonphaseView onAppear
...

Every complication calls onAppear, mutliple times even! Even those complications that are not presented on any face! Looks like the heavy rendering is done somewhere out of screen.


Solution

  • After further exploration and communication with Apple Tech Sup we found that the crashes are caused by the long list of WidgetKit complications. When the complications timeline is generated, all those views are loaded into memory to take PNG snapshot of every view. That's why we see PNG-related functions in the crash stack. Due to some internal logic that views are not released and continue to take memory, as WidgetArchiver goes through the timeline.

    The solution in this case is to use few configurable widgets (AppIntentConfiguration or legacy IntentConfiguration) with number of options, instead of packing that number of widgets into WidgetBundle. This way we fixed the crash, and we suppose other apps like Clockology use the same approach, enjoy!