Weak Reference Management for OBS Objects: Complete Guide to obs_weak_* Types
OBS Studio implements thread-safe weak reference management for objects with obs_weak_* types using atomic reference counting, allowing non-owning handles to sources, outputs, and encoders without preventing garbage collection or risking use-after-free errors.
Weak reference management for objects with obs_weak_* types forms the backbone of OBS Studio's memory safety architecture, enabling plugins and core modules to track object lifetimes without retaining ownership. The obsproject/obs-studio repository implements this pattern through the libobs subsystem, providing atomic reference counting for core objects including sources, outputs, encoders, services, and canvases.
Why Weak References Matter in OBS Studio
OBS Studio operates across multiple threads—audio callbacks, video rendering, and UI events all access shared objects concurrently. Strong references (traditional ownership) would force every subsystem to coordinate destruction, creating complex circular dependencies. Weak references solve this by allowing code to hold a handle that can detect when an object dies without preventing that destruction.
The obs_weak_* system provides three critical guarantees:
- Non-blocking expiration detection: Check if an object was destroyed without acquiring locks.
- Safe promotion: Atomically upgrade a weak handle to a strong reference if the object still exists.
- Automatic cleanup: Weak control blocks free themselves when all references (strong and weak) disappear.
Core Architecture of obs_weak_* Types
The obs_weak_ref Structure
All weak types embed a common atomic control block defined in libobs/obs-internal.h:
struct obs_weak_ref {
volatile long refs; // strong reference count
volatile long weak_refs; // weak reference count
};
This structure lives in the object's control block, allocated at creation time. The volatile qualifier ensures compiler optimizations do not reorder atomic operations across threads.
Specialized Weak Types
OBS defines type-specific weak structures for each core object, all following the same pattern:
| Weak Type | Underlying Object | Definition Location |
|---|---|---|
obs_weak_object_t |
obs_object_t |
libobs/obs-internal.h |
obs_weak_source_t |
obs_source_t |
libobs/obs-internal.h |
obs_weak_output_t |
obs_output_t |
libobs/obs-internal.h |
obs_weak_encoder_t |
obs_encoder_t |
libobs/obs-internal.h |
obs_weak_service_t |
obs_service_t |
libobs/obs-internal.h |
obs_weak_canvas_t |
obs_canvas_t |
libobs/obs-internal.h |
Each structure contains exactly two members: an obs_weak_ref and a pointer to the underlying object. For example, the source variant:
struct obs_weak_source {
struct obs_weak_ref ref;
struct obs_source *source;
};
Internal Reference Counting API
The atomic operations governing weak references reside in libobs/obs-internal.h (lines 75-111). These functions operate directly on obs_weak_ref structures:
obs_ref_addref/obs_ref_release: Manipulate the strong reference count (refs). Whenrefsreaches-1, the object enters a destroyed state.obs_weak_ref_addref/obs_weak_ref_release: Manipulate the weak reference count (weak_refs). When both counts reach their terminal values, the control block frees itself viabfree.obs_weak_ref_get_ref: Attempts to increment the strong count if the object is still alive (atomic compare-and-swap operation). Returnstrueif successful,falseif the object was already destroyed.obs_weak_ref_expired: Returnstrueifrefs < 0, indicating the underlying object has been destroyed.
Public API for Weak Reference Management
The header libobs/obs.h exposes type-safe wrappers for each object category. These functions hide the internal obs_weak_ref mechanics while providing the same atomic guarantees:
/* Generic object weak references */
EXPORT void obs_weak_object_addref(obs_weak_object_t *weak);
EXPORT void obs_weak_object_release(obs_weak_object_t *weak);
EXPORT obs_weak_object_t *obs_object_get_weak_object(obs_object_t *object);
EXPORT obs_object_t *obs_weak_object_get_object(obs_weak_object_t *weak);
EXPORT bool obs_weak_object_expired(obs_weak_object_t *weak);
EXPORT bool obs_weak_object_references_object(obs_weak_object_t *weak,
obs_object_t *object);
/* Source-specific variants */
EXPORT obs_weak_source_t *obs_source_get_weak_source(obs_source_t *source);
EXPORT obs_source_t *obs_weak_source_get_source(obs_weak_source_t *weak);
EXPORT void obs_weak_source_release(obs_weak_source_t *weak);
/* ... and similar for output, encoder, service, canvas */
Lifecycle Operations
Creating Weak References
When an object is created, its obs_context_data structure receives a pointer to a newly allocated weak control block. The macro get_weak retrieves this control block from the context:
#define get_weak(source) ((obs_weak_source_t *)source->context.control)
To obtain a weak handle from a strong pointer:
obs_weak_source_t *weak = obs_source_get_weak_source(source);
This function, defined in libobs/obs-source.c, increments the weak reference count before returning the handle.
Promoting to Strong References
The critical safety feature is the ability to atomically upgrade a weak reference to a strong one:
obs_source_t *src = obs_weak_source_get_source(weak);
Internally, this calls obs_weak_ref_get_ref in libobs/obs-internal.h. If the strong count is still valid (not -1), the function atomically increments it and returns the underlying pointer. If the object was destroyed between the check and the increment, it returns NULL.
Detecting Expiration
Before attempting promotion, you can check if the object still exists:
if (obs_weak_source_expired(weak)) {
/* Object is already destroyed */
}
This checks if refs < 0 in the control block, indicating the destructor has run.
Cleanup and Release
When finished with a weak handle, release it to prevent memory leaks:
obs_weak_source_release(weak);
This decrements the weak reference count. When both the strong and weak counts reach their terminal values, the control block automatically frees itself via bfree.
Practical Implementation Patterns
Pattern 1: Callback Safety with Weak Sources
When registering callbacks that may fire after an object is destroyed, store a weak reference rather than a strong one:
/* Store weak handle during registration */
obs_weak_source_t *weak_src = obs_source_get_weak_source(my_source);
void my_callback(void *data, void *param)
{
obs_weak_source_t *weak = (obs_weak_source_t *)param;
/* Attempt promotion; returns NULL if destroyed */
obs_source_t *src = obs_weak_source_get_source(weak);
if (!src)
return; /* Source was destroyed between callback queue and execution */
/* Safe to use src here */
process_source_data(src);
/* Release the temporary strong reference */
obs_source_release(src);
}
/* Cleanup when unregistering */
obs_weak_source_release(weak_src);
This pattern appears throughout libobs/obs-source.c when handling source removal callbacks.
Pattern 2: Validity Checking for Outputs
UI code often needs to check if an output is still valid before updating status indicators:
obs_weak_output_t *weak_out = obs_output_get_weak_output(my_output);
/* Later, perhaps from a UI timer */
if (!obs_weak_output_expired(weak_out)) {
obs_output_t *out = obs_weak_output_get_output(weak_out);
/* Safe to query output status */
bool active = obs_output_active(out);
update_ui_status(active);
obs_output_release(out);
} else {
update_ui_status(false); /* Output destroyed */
}
obs_weak_output_release(weak_out);
This avoids crashes when outputs are destroyed while UI callbacks are pending.
Key Source Files and Implementation Details
| File | Role | Key Components |
|---|---|---|
libobs/obs-internal.h |
Core weak reference structures | struct obs_weak_ref, obs_ref_addref, obs_weak_ref_get_ref |
libobs/obs.h |
Public API declarations | obs_weak_object_addref, obs_source_get_weak_source, obs_weak_output_get_output |
libobs/obs.c |
Generic object weak reference implementation | obs_weak_object_get_object, obs_weak_object_expired |
libobs/obs-source.c |
Source-specific weak reference management | obs_source_get_weak_source, get_weak macro |
libobs/obs-output.c |
Output weak reference implementation | obs_output_get_weak_output, obs_weak_output_release |
libobs/obs-encoder.c |
Encoder weak reference handling | obs_encoder_get_weak_encoder |
libobs/obs-service.c |
Service weak reference functions | obs_service_get_weak_service |
libobs/obs-canvas.c |
Canvas weak reference management | obs_canvas_get_weak_canvas |
Summary
- Atomic safety: The
obs_weak_refstructure usesvolatile longcounters with atomic operations to ensure thread-safe reference counting across OBS's multi-threaded architecture. - Non-owning handles: Weak references allow callbacks and UI code to track object lifetimes without preventing destruction, solving circular dependency issues in scene graphs.
- Promotion pattern: Always use
obs_weak_*_get_*functions to attempt atomic promotion from weak to strong references, checking forNULLreturns before dereferencing. - Expiration detection: Use
obs_weak_*_expiredto check object status without creating temporary strong references, useful for UI polling scenarios. - Mandatory cleanup: Always pair
obs_*_get_weak_*calls withobs_weak_*_releaseto prevent memory leaks in the control blocks.
Frequently Asked Questions
How do I safely access an object after storing a weak reference?
Always attempt to promote the weak reference to a strong one using the appropriate obs_weak_*_get_* function. For example, call obs_weak_source_get_source(weak) and check if the return value is non-NULL. If successful, you receive a strong reference that prevents destruction while you use it. Remember to call obs_source_release (or the appropriate release function) when finished to decrement the strong count.
What happens if I call obs_weak_source_get_source after the source is destroyed?
The function returns NULL. Internally, obs_weak_ref_get_ref checks the strong reference count in the control block. If the count indicates the object has been destroyed (values less than 0), the atomic compare-and-swap operation fails, and the promotion function returns NULL instead of a dangling pointer. This makes weak references safe to use even in asynchronous callbacks where the object might disappear between check and use.
Can I check if a weak reference is valid without creating a strong reference?
Yes. Use the obs_weak_*_expired functions (such as obs_weak_output_expired or obs_weak_source_expired). These check if the strong reference count has dropped below zero, indicating the underlying object has been destroyed. This is useful for UI polling or logging where you want to know status without the overhead of temporarily incrementing the strong reference count. However, for actual object access, you must still use the promotion functions to obtain a strong reference.
Where are the weak reference control blocks allocated and freed?
Control blocks are allocated when objects are created (e.g., during obs_source_create or obs_output_create) and stored in the obs_context_data structure's control pointer. The block persists until both strong and weak reference counts reach terminal values. When the last strong reference is released, the object destructor runs but the control block remains if weak references exist. Once the last weak reference is released via obs_weak_*_release, the control block is freed using bfree in libobs/obs-internal.h.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →