Meta faced significant challenges maintaining a forked version of WebRTC across 50+ use cases within its monorepo, leading to drift from upstream. This article details their multi-year migration to a modular, dual-stack architecture that allows simultaneous operation and A/B testing of both legacy and upstream WebRTC versions, mitigating symbol collisions and managing proprietary patches. The solution improved performance, binary size, and security, while establishing a continuous upgrade pipeline.
Read original on Meta EngineeringForking a large open-source project like WebRTC in a monorepo, while initially offering control for optimizations and bug fixes, inevitably leads to significant technical debt. As the upstream project evolves, merging external commits becomes prohibitively expensive, leading to a 'forking trap' where internal development diverges, making upgrades difficult and costly. Meta's experience highlights the need for a robust strategy to integrate custom changes while staying current with upstream to avoid performance, security, and maintenance issues across billions of users and diverse use cases like Messenger, Instagram video calls, Cloud Gaming, and VR casting.
To escape the forking trap and enable safe, gradual migration, Meta engineered a dual-stack architecture. This involved running two versions of WebRTC (legacy and latest upstream with Meta's patches) simultaneously within the same application. The core of this solution is a shim layer acting as a proxy between the application code and the WebRTC implementations. This shim exposes a unified, version-agnostic API, dispatching calls at runtime to either the legacy or latest WebRTC flavor based on a global configuration flag. This design was crucial for A/B testing new upstream releases on production traffic without disrupting user experience.
Shim Layer Benefits
The shim layer was a critical architectural decision. By introducing a thin abstraction layer at the lowest possible point, Meta significantly reduced binary size overhead compared to duplicating higher-level application logic. This pattern is valuable for migrating complex, deeply integrated components where direct replacement is too risky or disruptive.
Statically linking two versions of WebRTC presented a major challenge: C++ linker's One Definition Rule (ODR) violations due to duplicate symbols. Meta's solution involved automated renamespacing, systematically rewriting C++ namespaces (e.g., `webrtc::` to `webrtc_latest::` and `webrtc_legacy::`) and manipulating global symbols with flavor-specific identifiers. To maintain backward compatibility for existing call sites, they used C++ `using` declarations to import the active flavor's namespace into the familiar `webrtc::` namespace, allowing external engineers to continue writing code as before.
The scale of WebRTC (dozens of APIs, numerous objects) made manual shim creation impractical. Meta developed a code generation system using AST parsing to automate the creation of baseline shim code, significantly increasing velocity and reducing errors. For managing proprietary patches in a monorepo without widespread branching support, Meta chose to track feature branches in a separate Git repository. This approach allowed for clear ownership of patches and facilitated continuous rebasement on top of upstream, ensuring a smooth path for contributing fixes back to the open-source project.