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). When refs reaches -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 via bfree.
  • obs_weak_ref_get_ref: Attempts to increment the strong count if the object is still alive (atomic compare-and-swap operation). Returns true if successful, false if the object was already destroyed.
  • obs_weak_ref_expired: Returns true if refs < 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_ref structure uses volatile long counters 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 for NULL returns before dereferencing.
  • Expiration detection: Use obs_weak_*_expired to check object status without creating temporary strong references, useful for UI polling scenarios.
  • Mandatory cleanup: Always pair obs_*_get_weak_* calls with obs_weak_*_release to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →