macOS: every obs-websocket GetStats leaks ~200 autoreleased objects (os_get_free_space has no autorelease pool on the websocket thread) — ~2 MB/min at one poll per second
Nobody has claimed this yet.
Assessment
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Newbie friendliness
- 92/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- objective-c
- Domain
- desktop, performance
Research direction
Start in libobs/util/platform-cocoa.m at os_get_free_space, then review how GetStats reaches it on the obs-websocket thread. Reproduce with OBJC_DEBUG_MISSING_POOLS=YES and repeated GetStats requests; done means the diagnostic reports no missing pools and repeated polling no longer causes unbounded resident-memory growth.
Written by the indexing model from the issue text.
Description
Operating System Info
macOS 15
Other OS
macOS 15.7.9 (24G830), Intel (x86_64 build)
OBS Studio Version
32.2.2
OBS Studio Version (Other)
No response
OBS Studio Log URL
Not needed for this one: nothing is logged; the reproduction is a websocket client and the process's resident size. Happy to attach a log on request.
OBS Studio Crash Log URL
No response
Expected Behavior
An obs-websocket client that polls GetStats (a stats dashboard, a controller, any client that watches fps/skipped frames) does not make OBS's memory grow over time.
Current Behavior
On macOS, every GetStats answer leaves about 200 Foundation objects in memory that are never released. At one GetStats per second OBS grows by roughly 2 MB per minute for as long as the client is connected — linear, never plateaus. Two hours of polling: +280 MB resident.
Cause, from the source: GetStats calls os_get_free_disk_space() for availableDiskSpace; on macOS that is os_get_free_space() in libobs/util/platform-cocoa.m, which builds an NSURL, an NSArray of keys and the NSDictionary from -[NSURL resourceValuesForKeys:error:] (and everything that dictionary holds). All of it is autoreleased. obs-websocket answers the request on its own thread, which has no autorelease pool, so the objects are never released ("autoreleased with no pool in place - just leaking", in the runtime's words). The main thread has a pool and is unaffected, which is why the UI never shows this.
The function exists in the same form on master today.
Steps to Reproduce
- Launch OBS with the runtime's diagnostic for this exact condition:
OBJC_DEBUG_MISSING_POOLS=YES /Applications/OBS.app/Contents/MacOS/OBS 2> /tmp/obs.txt(any profile whose recording path exists — the default~/Moviesdoes; with a non-existent path the lookup fails early and allocates almost nothing). - Send ten
GetStatsrequests over obs-websocket. grep -c 'no pool in place' /tmp/obs.txt→ 1 980 new lines (198 per request). TenGetVersion,GetStreamStatus,GetRecordStatusorGetRecordDirectoryadd 0.- Send 3 000
GetStats(one at a time): resident size 228 → 340 MB, and it stays there;heapon the process shows +594 000 objects, mostly__NSArrayI_Transfer,__NSCFString,NSConcreteData,NSPathStore2,__NSDictionaryM;vmmap --summaryputs the growth inMALLOC_NANO.
Class breakdown per ten requests, from the diagnostic:
880 __NSArrayI_Transfer
532 __NSCFString
411 NSConcreteData
118 NSPathStore2
20 __NSDictionaryM
Anything else we should know?
The fix is an @autoreleasepool around the body of os_get_free_space (the other Cocoa helpers in that file already do this, e.g. os_request_high_performance). With it, the same ten requests add 0 diagnostic lines and 3 000 requests add about 1 MB. Patch against 32.2.2 (applies to master):
--- a/libobs/util/platform-cocoa.m
+++ b/libobs/util/platform-cocoa.m
@@ -361,22 +361,30 @@ uint64_t os_get_sys_free_size(void)
int64_t os_get_free_space(const char *path)
{
if (path) {
- NSURL *fileURL = [NSURL fileURLWithPath:@(path)];
-
- NSArray *availableCapacityKeys = @[
- NSURLVolumeAvailableCapacityKey, NSURLVolumeAvailableCapacityForImportantUsageKey,
- NSURLVolumeAvailableCapacityForOpportunisticUsageKey
- ];
-
- NSDictionary *values = [fileURL resourceValuesForKeys:availableCapacityKeys error:nil];
-
- NSNumber *availableImportantSpace = values[NSURLVolumeAvailableCapacityForImportantUsageKey];
- NSNumber *availableSpace = values[NSURLVolumeAvailableCapacityKey];
-
- if (availableImportantSpace.longValue > 0) {
- return availableImportantSpace.longValue;
- } else {
- return availableSpace.longValue;
+ /* Callers are not only the main thread: obs-websocket answers
+ * GetStats on its own thread, which has no autorelease pool, and
+ * every object autoreleased here (the URL, the key array, the
+ * resource dictionary and all it holds) would then live until that
+ * thread exits — about 200 objects per call, 2 MB a minute at one
+ * poll a second. */
+ @autoreleasepool {
+ NSURL *fileURL = [NSURL fileURLWithPath:@(path)];
+
+ NSArray *availableCapacityKeys = @[
+ NSURLVolumeAvailableCapacityKey, NSURLVolumeAvailableCapacityForImportantUsageKey,
+ NSURLVolumeAvailableCapacityForOpportunisticUsageKey
+ ];
+
+ NSDictionary *values = [fileURL resourceValuesForKeys:availableCapacityKeys error:nil];
+
+ NSNumber *availableImportantSpace = values[NSURLVolumeAvailableCapacityForImportantUsageKey];
+ NSNumber *availableSpace = values[NSURLVolumeAvailableCapacityKey];
+
+ if (availableImportantSpace.longValue > 0) {
+ return availableImportantSpace.longValue;
+ } else {
+ return availableSpace.longValue;
+ }
}
}
The same applies to any other Cocoa code reached from obs-websocket's thread; os_get_free_space is the one GetStats hits every time. Glad to open this as a PR if that is preferred.
— filed by the Rigger project (an application built on OBS)
- Dominant language
- C
- Stars
- 76.4k
- Forks
- 10.2k
- Avg merge
- 4d 23h
- Merged PRs (30d)
- 12
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from obsproject/obs-studio
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
obsproject/obs-studio#13905 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
obsproject/obs-studio#13896 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
obsproject/obs-studio#13823 · 4 comments ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
obsproject/obs-studio#13809 · 1 comment ·
-
Difficulty 2/5 Half a day Newbie friendliness 68/100
obsproject/obs-studio#13773 · 2 comments ·
All issues in obsproject/obs-studio
Similar issues
-
level/task module/gcp type/bug
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
docs
Difficulty 1/5 Under an hour Newbie friendliness 85/100
-
P3 sonic-vpp
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
sonic-net/sonic-buildimage#29662 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 94/100
spack/spack-packages#6586 ·
-
category:port-update
Difficulty 2/5 1-3 hours Newbie friendliness 72/100