fix: Avoid keeping strong reference to self instance in delegates#1123
fix: Avoid keeping strong reference to self instance in delegates#1123mykola-mokhnach merged 9 commits intomasterfrom
Conversation
|
Interesting. I'll take a look at it and run some tests on Sunday once I'm back at work. |
There was a problem hiding this comment.
Pull request overview
This PR aims to reduce retention/teardown issues in the MJPEG screenshot streaming pipeline and web server shutdown flow by adding explicit stop handling, weakening delegate references, bounding streaming write timeouts, and coalescing image scaling work.
Changes:
- Add
stopStreaming+ run-state tracking toFBMjpegServer, and enforce bounded socket write timeouts with periodic stats logging. - Coalesce scaling work in
FBImageProcessorso repeated submissions don’t schedule redundant scaling blocks. - Break potential retention chains during shutdown by weakening
FBTCPSocket.delegate, clearing delegates on stop, and avoiding strongselfcapture in route blocks.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| WebDriverAgentLib/Utilities/FBMjpegServer.m | Adds streaming lifecycle flag, bounded write timeout, periodic stats, and explicit shutdown behavior. |
| WebDriverAgentLib/Utilities/FBMjpegServer.h | Exposes stopStreaming API for orderly shutdown. |
| WebDriverAgentLib/Utilities/FBImageProcessor.m | Coalesces/drains scaling work via a scheduled flag and a loop on the scaling queue. |
| WebDriverAgentLib/Routing/FBWebServer.m | Ensures broadcaster shutdown on dealloc/stop and avoids strong self capture in route blocks. |
| WebDriverAgentLib/Routing/FBTCPSocket.m | Makes delegate callbacks nil-safe and clears delegate on stop. |
| WebDriverAgentLib/Routing/FBTCPSocket.h | Changes delegate to weak (when supported) to prevent retention. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| self.nextImage = image; | ||
| BOOL shouldSchedule = !self.isScalingScheduled; | ||
| if (shouldSchedule) { | ||
| self.isScalingScheduled = YES; | ||
| } | ||
| [self.nextImageLock unlock]; | ||
| if (!shouldSchedule) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
submitImageData: now coalesces/schedules scaling work via isScalingScheduled and a draining while loop. Existing FBImageProcessorTests cover scaling correctness but don’t appear to exercise the new scheduling/coalescing behavior (e.g., multiple rapid submitImageData: calls resulting in expected number/order of completion callbacks). Adding a test for the new queueing/coalescing semantics would help prevent regressions.
|
I haven't dug into it deeply yet, but this branch didn't generate any images for |
yes, there was a bug related to weak referencing - should be ok now |
|
(I'll test out again tonight) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| id<FBTCPSocketDelegate> delegate = self.screenshotsBroadcaster.delegate; | ||
| if ([(NSObject *)delegate respondsToSelector:@selector(stopStreaming)]) { | ||
| [(FBMjpegServer *)delegate stopStreaming]; |
There was a problem hiding this comment.
delegate is typed as id<FBTCPSocketDelegate>, but this code checks respondsToSelector: via an NSObject * cast and then force-casts to FBMjpegServer * to call stopStreaming. This is brittle (and will crash if a different delegate type is ever used). Prefer calling stopStreaming on the strongly-held self.mjpegServer, or narrow the type check/cast safely before invoking the method.
| id<FBTCPSocketDelegate> delegate = self.screenshotsBroadcaster.delegate; | |
| if ([(NSObject *)delegate respondsToSelector:@selector(stopStreaming)]) { | |
| [(FBMjpegServer *)delegate stopStreaming]; | |
| if (nil != self.mjpegServer) { | |
| [self.mjpegServer stopStreaming]; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const proxyTimeout = noSessionProxy.timeout; | ||
| noSessionProxy.timeout = 1000; | ||
| (noSessionProxy as any).timeout = 1000; | ||
| try { | ||
| currentStatus = (await noSessionProxy.command('/status', 'GET')) as StringRecord; | ||
| if (currentStatus && currentStatus.ios && (currentStatus.ios as any).ip) { | ||
| this.agentUrl = (currentStatus.ios as any).ip; | ||
| if (currentStatus?.ios?.ip) { | ||
| this.agentUrl = currentStatus.ios.ip as string | undefined; | ||
| } | ||
| this.log.debug(`WebDriverAgent information:`); | ||
| this.log.debug(JSON.stringify(currentStatus, null, 2)); | ||
| } catch (err: any) { | ||
| throw new Error(`Unable to connect to running WebDriverAgent: ${err.message}`); | ||
| } finally { | ||
| noSessionProxy.timeout = proxyTimeout; | ||
| (noSessionProxy as any).timeout = proxyTimeout; | ||
| } |
There was a problem hiding this comment.
The timeout mutation is now done via (noSessionProxy as any).timeout, which bypasses the type system and could mask a real API change (e.g., if timeout becomes a getter-only property, this assignment may throw at runtime or silently shadow the intended value). Prefer a typed approach here, such as creating a short-timeout NoSessionProxy instance just for the /status polling, or extending NoSessionProxy/JWProxy typings with a supported way to set the request timeout.
Dan-Maor
left a comment
There was a problem hiding this comment.
Went through the code and tested on the devices I have available with Xcode 16.2 and 26.3.
Looks good 👍
## [11.4.2](v11.4.1...v11.4.2) (2026-04-12) ### Bug Fixes * Avoid keeping strong reference to self instance in delegates ([#1123](#1123)) ([dd15f48](dd15f48))
|
🎉 This PR is included in version 11.4.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This PR adds explicit MJPEG stop/run-state handling, breaks retention chains during shutdown (delegate/server/broadcaster cleanup), and improves load behavior by bounding MJPEG write timeouts and coalescing image-scaling work. It also updates route blocks to avoid strong
selfcapture where applicable.Result: cleaner teardown, lower risk of retained streaming objects, and more stable memory usage during long-running/slow-client streaming sessions.