Bug 231162

Summary: ObjectiveC WebRTC frame buffers are autoreleased late, especially on Debug builds
Product: WebKit Reporter: youenn fablet <youennf>
Component: WebRTCAssignee: youenn fablet <youennf>
Status: RESOLVED FIXED    
Severity: Normal CC: ddkilzer, eric.carlson, ews-watchlist, ggaren, glenn, jer.noble, philipj, sergio, webkit-bug-importer, youennf
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: Unspecified   
OS: Unspecified   
Attachments:
Description Flags
Explicit close approach
none
Patch for landing none

youenn fablet
Reported 2021-10-04 03:03:48 PDT
ObjectiveC WebRTC frame buffers are autoreleased late, especially on Debug builds. On debug builds, running https://webrtc.github.io/samples/src/content/peerconnection/change-codecs/ with VP8 and mock capture source (click start, then call) will see memory increases a lot, up to more than 1 GB. When clicking hang up, all this memory got autoreleased but we would hope it would be released sooner. On debug builds, running https://webrtc.github.io/samples/src/content/peerconnection/pc1/ with the real camera fails after a few frames being encoded (click start, then call). The camera CVPixelBuffers are only released after clicking hang up, which makes the camera stopping to emit frames.
Attachments
Explicit close approach (7.38 KB, patch)
2021-10-04 03:19 PDT, youenn fablet
no flags
Patch for landing (7.54 KB, patch)
2021-10-06 00:28 PDT, youenn fablet
no flags
youenn fablet
Comment 1 2021-10-04 03:19:24 PDT
Created attachment 440047 [details] Explicit close approach
youenn fablet
Comment 2 2021-10-04 03:36:12 PDT
For the real camera case, RTCCVPixelBuffer are sometimes deallocated too late, and deallocation triggers releasing the wrapped CVPixelBuffer. For the VP8 case, CVPixelBuffers are converted in I420 webrtc internal buffers, which are kept alive until RTCI420Buffer are deallocated. I feel like we might be able to do better (@autoreleasepool maybe?) but I am not familiar with how to do it. Advices most welcome.
youenn fablet
Comment 3 2021-10-04 03:36:43 PDT
I wonder whether this might explain rdar://83435926.
youenn fablet
Comment 4 2021-10-04 05:27:02 PDT
Also not sure why the issue occurs now.
David Kilzer (:ddkilzer)
Comment 5 2021-10-04 17:53:51 PDT
Comment on attachment 440047 [details] Explicit close approach View in context: https://bugs.webkit.org/attachment.cgi?id=440047&action=review > Source/ThirdParty/libwebrtc/Source/webrtc/sdk/WebKit/WebKitEncoder.mm:374 > - auto *videoFrame = [[RTCVideoFrame alloc] initWithBuffer:ToObjCVideoFrameBuffer(pixelBufferToFrame(pixelBuffer)) rotation:RTCVideoRotation(rotation) timeStampNs:timeStampNs]; > + auto videoFrameBuffer = pixelBufferToFrame(pixelBuffer); > + auto *videoFrame = [[RTCVideoFrame alloc] initWithBuffer:ToObjCVideoFrameBuffer(videoFrameBuffer) rotation:RTCVideoRotation(rotation) timeStampNs:timeStampNs]; This change is likely what fixes the autoreleased objects. I'd have to look at generated code to be sure (which I'm not very good at), but this change probably allows the compiler to avoid autoreleasing the result of ToObjCVideoFrameBuffer() somehow, which is where these objects come from. Under ARC, any method that returns an Objective-C object (that's not marked NS_RETURNS_RETAINED) will return a +1 autoreleased object unless the caller is also compiled with ARC and the result is stored in some kind of a (temp) variable, which allows the +1 autoreleased object to become a +1 retained object using a special optimization trick. I would have said that this change would be a "safer" bet to eliminate the autoreleased object because it provides a local variable for the Objective-C object that was formerly autoreleased: auto videoFrameBuffer = ToObjCVideoFrameBuffer(pixelBufferToFrame(pixelBuffer)); // Obj-C object is now +1 retained instead of +1 autoreleased auto *videoFrame = [[RTCVideoFrame alloc] initWithBuffer:videoFrameBuffer rotation:RTCVideoRotation(rotation) timeStampNs:timeStampNs]; Having said that, changing ToObjCVideoFrameBuffer() into an inline method (moving it from objc_frame_buffer.mm to objc_frame_buffer.h) might also allow the optimization to occur (without changes to this caller), but I'm not 100% sure about that. (If ToObjCVideoFrameBuffer() is used in more than one place, this might eliminate other places where videoFrameBuffer objects are autoreleased as well.) (I used to have a blog article that I linked to where I learned about the optimization, but I can't find it now.)
David Kilzer (:ddkilzer)
Comment 6 2021-10-04 17:58:08 PDT
David Kilzer (:ddkilzer)
Comment 7 2021-10-04 18:06:01 PDT
(In reply to David Kilzer (:ddkilzer) from comment #6) > Ah, here's the article where I read about this: > <https://mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference- > counting.html> See the sections about objc_retainAutoreleaseReturnValue().
Radar WebKit Bug Importer
Comment 8 2021-10-05 01:24:05 PDT
youenn fablet
Comment 9 2021-10-05 03:43:26 PDT
Comment on attachment 440047 [details] Explicit close approach View in context: https://bugs.webkit.org/attachment.cgi?id=440047&action=review >> Source/ThirdParty/libwebrtc/Source/webrtc/sdk/WebKit/WebKitEncoder.mm:374 >> + auto *videoFrame = [[RTCVideoFrame alloc] initWithBuffer:ToObjCVideoFrameBuffer(videoFrameBuffer) rotation:RTCVideoRotation(rotation) timeStampNs:timeStampNs]; > > This change is likely what fixes the autoreleased objects. > > I'd have to look at generated code to be sure (which I'm not very good at), but this change probably allows the compiler to avoid autoreleasing the result of ToObjCVideoFrameBuffer() somehow, which is where these objects come from. > > Under ARC, any method that returns an Objective-C object (that's not marked NS_RETURNS_RETAINED) will return a +1 autoreleased object unless the caller is also compiled with ARC and the result is stored in some kind of a (temp) variable, which allows the +1 autoreleased object to become a +1 retained object using a special optimization trick. > > I would have said that this change would be a "safer" bet to eliminate the autoreleased object because it provides a local variable for the Objective-C object that was formerly autoreleased: > > auto videoFrameBuffer = ToObjCVideoFrameBuffer(pixelBufferToFrame(pixelBuffer)); // Obj-C object is now +1 retained instead of +1 autoreleased > auto *videoFrame = [[RTCVideoFrame alloc] initWithBuffer:videoFrameBuffer rotation:RTCVideoRotation(rotation) timeStampNs:timeStampNs]; > > Having said that, changing ToObjCVideoFrameBuffer() into an inline method (moving it from objc_frame_buffer.mm to objc_frame_buffer.h) might also allow the optimization to occur (without changes to this caller), but I'm not 100% sure about that. (If ToObjCVideoFrameBuffer() is used in more than one place, this might eliminate other places where videoFrameBuffer objects are autoreleased as well.) > > (I used to have a blog article that I linked to where I learned about the optimization, but I can't find it now.) I tried that but it does not seem to work for me.
youenn fablet
Comment 10 2021-10-06 00:22:35 PDT
Thanks for the r+, I will add some WEBRTC_WEBKIT_BUILD to keep track of the changes.
youenn fablet
Comment 11 2021-10-06 00:28:55 PDT
Created attachment 440340 [details] Patch for landing
EWS
Comment 12 2021-10-06 03:13:22 PDT
Committed r283610 (242562@main): <https://commits.webkit.org/242562@main> All reviewed patches have been landed. Closing bug and clearing flags on attachment 440340 [details].
youenn fablet
Comment 13 2021-10-06 05:20:33 PDT
youenn fablet
Comment 14 2021-10-06 05:20:59 PDT
Note You need to log in before you can comment on or make changes to this bug.