Conversation
Summary: Switch unnecessary copy constructors with move semantics
Type: Fix
Test Plan: UT/CT, Fullstack
Jira: RDKEMW-12966
Summary: Switch unnecessary copy constructors with move semantics
Type: Fix
Test Plan: UT/CT, Fullstack
Jira: RDKEMW-12966
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
Summary: Fixing missing const ref arguments
Type: Fix
Test Plan: UT/CT, Fullstack
Jira: RDKEMW-12966
Summary: Fixing missing const ref arguments
Type: Fix
Test Plan: UT/CT, Fullstack
Jira: RDKEMW-12966
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
…into coverity-improvements
Summary: Adding missing includes
Type: Fix
Test Plan: UT/CT, Fullstack
Jira: RDKEMW-12966
Summary: Finding potential memory leak points and adding fixes
Type: Fix
Test Plan: UT/CT, Fullstack
Jira: RDKEMW-15495
|
Pull request must be merged with a description containing the required fields, Summary: If there is no jira releated to this change, please put 'Jira: NO-JIRA'. Description can be changed by editing the top comment on your pull request and making a new commit. |
Summary: Fixing issues with modified code for memory leak Type: Fix Test Plan: UT/CT, Fullstack Jira: RDKEMW-15495
There was a problem hiding this comment.
Pull request overview
This PR focuses on eliminating several potential memory-leak and lifetime-extension patterns across the Rialto server/client stack by tightening ownership semantics (moves/const refs), cleaning up internal state on teardown, and improving synchronization around task/timer lifecycles.
Changes:
- Reduce unintended refcount/lifetime extension by switching several APIs/ctors to
const std::shared_ptr<...>&and adding missingstd::move(...)where rvalue refs are accepted. - Fix/avoid leaks by ensuring resources are released on failure paths and by reworking
ActiveRequestssegment buffer ownership. - Improve concurrency correctness by adding completion predicates to main-thread task waits and narrowing lock scopes in IPC/event-thread code.
Reviewed changes
Copilot reviewed 68 out of 68 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unittests/media/server/mocks/main/MainThreadMock.h | Update mock signatures to const Task& |
| tests/unittests/ipc/mocks/IpcServerMock.h | Update mock signatures to const std::function& |
| serverManager/serverManagerSim/HttpRequest.cpp | Move strings into split results |
| media/server/service/source/WebAudioPlayerService.cpp | Move factory into member |
| media/server/service/source/MediaPipelineService.cpp | Move factory into member |
| media/server/service/source/CdmService.cpp | Move factories into members |
| media/server/main/source/MediaPipelineServerInternal.cpp | Const-ref params; erase more per-source state |
| media/server/main/source/MediaPipelineCapabilities.cpp | Const-ref shared_ptr param |
| media/server/main/source/MediaKeysServerInternal.cpp | Const-ref shared_ptr params |
| media/server/main/source/MediaKeysCapabilities.cpp | Const-ref shared_ptr params |
| media/server/main/source/MediaKeySession.cpp | Move captures into lambdas |
| media/server/main/source/MainThread.cpp | Predicate waits; const-ref task args |
| media/server/main/source/ActiveRequests.cpp | Store segment buffers; ID wrap handling |
| media/server/main/interface/IMainThread.h | Task APIs take const Task& |
| media/server/main/include/MediaPipelineServerInternal.h | Match ctor signature updates |
| media/server/main/include/MediaPipelineCapabilities.h | Match ctor signature updates |
| media/server/main/include/MediaKeysServerInternal.h | Match ctor signature updates |
| media/server/main/include/MediaKeysCapabilities.h | Match ctor signature updates |
| media/server/main/include/MainThread.h | Add done flag; const-ref task args |
| media/server/main/include/ActiveRequests.h | Add m_segmentBuffers ownership |
| media/server/ipc/source/MediaPipelineModuleService.cpp | Move config vectors into AudioConfig |
| media/server/ipc/include/ControlModuleService.h | Avoid client lifetime extension via weak_ptr key |
| media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp | Pass wrappers by const-ref |
| media/server/gstplayer/source/tasks/webAudio/SetVolume.cpp | Pass wrapper by const-ref |
| media/server/gstplayer/source/tasks/webAudio/SetCaps.cpp | Pass wrappers by const-ref |
| media/server/gstplayer/source/tasks/webAudio/HandleBusMessage.cpp | Pass wrappers by const-ref |
| media/server/gstplayer/source/tasks/webAudio/Eos.cpp | Pass wrapper by const-ref |
| media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp | Pass wrappers by const-ref |
| media/server/gstplayer/source/tasks/generic/SetupElement.cpp | Pass wrappers by const-ref |
| media/server/gstplayer/source/tasks/generic/SetVolume.cpp | Pass wrappers by const-ref |
| media/server/gstplayer/source/tasks/generic/SetTextTrackIdentifier.cpp | Pass wrapper by const-ref |
| media/server/gstplayer/source/tasks/generic/SetPosition.cpp | Pass wrapper by const-ref |
| media/server/gstplayer/source/tasks/generic/SetPlaybackRate.cpp | Pass wrappers by const-ref |
| media/server/gstplayer/source/tasks/generic/SetMute.cpp | Pass wrappers by const-ref |
| media/server/gstplayer/source/tasks/generic/ProcessAudioGap.cpp | Pass wrapper by const-ref |
| media/server/gstplayer/source/tasks/generic/Eos.cpp | Pass wrapper by const-ref |
| media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp | Pass wrapper by const-ref |
| media/server/gstplayer/source/tasks/generic/AttachSamples.cpp | Move structs into vectors |
| media/server/gstplayer/source/GstGenericPlayer.cpp | Unref buffer on metadata failure |
| media/server/gstplayer/source/GstCapabilities.cpp | Move property name into output |
| media/server/gstplayer/source/CapsBuilder.cpp | Pass wrappers by const-ref |
| media/server/gstplayer/include/tasks/webAudio/WriteBuffer.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/webAudio/SetVolume.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/webAudio/SetCaps.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/webAudio/HandleBusMessage.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/webAudio/Eos.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/generic/UpdatePlaybackGroup.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/generic/SetupElement.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/generic/SetVolume.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/generic/SetTextTrackIdentifier.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/generic/SetPosition.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/generic/SetPlaybackRate.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/generic/SetMute.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/generic/ProcessAudioGap.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/generic/Eos.h | Match ctor signature updates |
| media/server/gstplayer/include/tasks/generic/CheckAudioUnderflow.h | Match ctor signature updates |
| media/server/gstplayer/include/CapsBuilder.h | Match ctor signature updates |
| media/client/main/source/MediaPipeline.cpp | Move request into map |
| media/client/main/source/Control.cpp | Move shared_ptr into vector |
| media/client/main/source/ClientController.cpp | Move shared_ptr into vector |
| media/client/ipc/source/IpcClient.cpp | Move channel into poll closure |
| logging/source/EnvVariableParser.cpp | Move tail token into output |
| ipc/server/source/IpcServerImpl.h | Callback params by const-ref |
| ipc/server/source/IpcServerImpl.cpp | Callback params by const-ref; tighter locking |
| ipc/server/include/IIpcServer.h | Callback params by const-ref |
| ipc/client/source/IpcChannelImpl.cpp | Reduce lock scope; keep timer updates under lock |
| common/source/Timer.cpp | Don’t hold mutex while executing callback; cancel wakes thread |
| common/source/EventThread.cpp | Tighter lock scope in destructor |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ActiveRequests::ActiveRequestsData::~ActiveRequestsData() | ||
| { | ||
| for (std::unique_ptr<IMediaPipeline::MediaSegment> &segment : m_segments) | ||
| { | ||
| delete[] segment->getData(); | ||
| } | ||
| RIALTO_SERVER_LOG_DEBUG("ActiveRequestsData destructor called"); | ||
| } |
There was a problem hiding this comment.
ActiveRequestsData::~ActiveRequestsData() now logs on every destruction. If ActiveRequestsData objects are created/erased frequently, this can generate a lot of debug logging and overhead without providing actionable information. Consider removing this log or only logging under a specific diagnostic flag.
| IMediaPipeline::MediaSegmentVector m_segments; | ||
| std::vector<std::vector<uint8_t>> m_segmentBuffers; | ||
| }; |
There was a problem hiding this comment.
ActiveRequests.h now declares m_segmentBuffers as std::vector<std::vector<uint8_t>>, but the header doesn’t include . This currently relies on transitive includes from other headers and can break compilation if include order changes. Add an explicit #include in this header.
2196f15 to
eb89341
Compare
…nto feature/RDKEMW-15495
eb89341 to
31be81b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 67 out of 67 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Coverage statistics of your commit: |
|
Coverage statistics of your commit: |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| std::atomic_bool cancelled{false}; | ||
| std::unique_ptr<ITimer> timer{ | ||
| ITimerFactory::getFactory()->createTimer(std::chrono::milliseconds{100}, [&]() { | ||
| timer->cancel(); | ||
| cancelled = true; | ||
| }, common::TimerType::ONE_SHOT)}; | ||
|
|
||
| std::this_thread::sleep_for(std::chrono::milliseconds{50}); |
There was a problem hiding this comment.
In this test, the lambda passes common::TimerType::ONE_SHOT, but there is no common namespace alias in this file (only TimerType is imported), so this won’t compile. Also, the timer is configured for 100ms but the test only sleeps for 50ms before asserting cancelled == true, which will be flaky/consistently fail depending on scheduling. Use TimerType::ONE_SHOT (or add an explicit namespace alias) and wait for the callback with a condition_variable (or sleep for > timeout with a generous margin) to make the test deterministic. You’ll also likely need an explicit <thread> include for std::this_thread::sleep_for to avoid relying on transitive headers.
| std::atomic_bool cancelled{false}; | |
| std::unique_ptr<ITimer> timer{ | |
| ITimerFactory::getFactory()->createTimer(std::chrono::milliseconds{100}, [&]() { | |
| timer->cancel(); | |
| cancelled = true; | |
| }, common::TimerType::ONE_SHOT)}; | |
| std::this_thread::sleep_for(std::chrono::milliseconds{50}); | |
| std::mutex mtx; | |
| std::condition_variable cv; | |
| bool cancelled{false}; | |
| std::unique_lock<std::mutex> lock{mtx}; | |
| std::unique_ptr<ITimer> timer{ | |
| ITimerFactory::getFactory()->createTimer( | |
| std::chrono::milliseconds{100}, | |
| [&]() | |
| { | |
| std::unique_lock<std::mutex> callbackLock{mtx}; | |
| timer->cancel(); | |
| cancelled = true; | |
| cv.notify_one(); | |
| }, | |
| TimerType::ONE_SHOT)}; | |
| cv.wait_for(lock, kEnoughTimeForTestToComplete); |
| std::unique_ptr<IMediaPipeline::MediaSegment> copiedSegment = segment->copy(); | ||
|
|
||
| uint8_t *data = new uint8_t[segment->getDataLength()]; | ||
| std::memcpy(data, segment->getData(), segment->getDataLength()); | ||
| m_segmentBuffers.emplace_back(segment->getDataLength()); | ||
| std::memcpy(m_segmentBuffers.back().data(), segment->getData(), segment->getDataLength()); | ||
|
|
||
| copiedSegment->setData(segment->getDataLength(), data); | ||
| copiedSegment->setData(m_segmentBuffers.back().size(), m_segmentBuffers.back().data()); | ||
| m_segments.push_back(std::move(copiedSegment)); |
There was a problem hiding this comment.
MediaSegment::setData() requires the passed buffer to remain valid for later use, but this implementation stores the data in m_segmentBuffers (a std::vector<std::vector<uint8_t>>) and then passes m_segmentBuffers.back().data() into the segment. Subsequent emplace_back() calls can reallocate/move the inner vectors, which can invalidate previously stored .data() pointers and lead to use-after-free/corrupted media data. Use a container with stable addresses for stored buffers (e.g., std::deque<std::vector<uint8_t>>), or store buffers behind stable indirection (e.g., std::shared_ptr<std::vector<uint8_t>> / std::unique_ptr<uint8_t[]>), and add/extend a unit test to add many segments and verify earlier segment data remains intact.
| m_currentId = 1; | ||
| } | ||
|
|
||
| auto [it, inserted] = m_requestMap.emplace(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes)); |
There was a problem hiding this comment.
auto [it, inserted] = ... declares it but it is never used. In Debug builds this repo enables -Wall -Werror (and doesn’t always disable -Werror=unused-variable), so this can fail compilation. Consider avoiding the structured binding here (e.g., use the .second from emplace) or otherwise mark/consume the unused variable.
| auto [it, inserted] = m_requestMap.emplace(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes)); | |
| bool inserted = m_requestMap.emplace(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes)).second; |
d71d447 to
02c9847
Compare
|
Coverage statistics of your commit: |
02c9847 to
9f691e9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); | ||
|
|
||
| EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, 100)); | ||
|
|
||
| EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); | ||
| EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(2)); |
There was a problem hiding this comment.
This test doesn't appear to exercise the new "duplicate id" behavior: the IDs returned (0, 1, 2) are the normal sequential IDs and would also pass with the previous implementation. To validate the fix, the test should force an actual ID collision (e.g., by arranging m_currentId to wrap/reuse an existing key, or by exposing a test hook to set m_currentId), and then assert that insert() returns a different unused ID and that the request map contains the expected entry.
| EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); | |
| EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, 100)); | |
| EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); | |
| EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(2)); | |
| // Insert three requests using the same external ID and ensure that they | |
| // are stored under distinct internal IDs without overwriting each other. | |
| const auto id1 = m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100); | |
| const auto id2 = m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, 100); | |
| const auto id3 = m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100); | |
| // At least the second and third inserts must not reuse the first ID. | |
| EXPECT_NE(id1, id2); | |
| EXPECT_NE(id1, id3); | |
| // All inserted entries must be retrievable with the correct type. | |
| EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(id1)); | |
| EXPECT_EQ(firebolt::rialto::MediaSourceType::VIDEO, m_sut.getType(id2)); | |
| EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(id3)); |
| std::shared_ptr<ITimer> timer; | ||
| timer = ITimerFactory::getFactory()->createTimer( | ||
| std::chrono::milliseconds{50}, | ||
| [&, weakTimer = std::weak_ptr<ITimer>(timer)]() | ||
| { | ||
| if (auto t = weakTimer.lock()) | ||
| { | ||
| t->cancel(); | ||
| } | ||
| { | ||
| std::lock_guard<std::mutex> lock{mtx}; | ||
| cancelled = true; | ||
| } | ||
| cv.notify_one(); | ||
| }, | ||
| TimerType::ONE_SHOT); |
There was a problem hiding this comment.
weakTimer is initialized from timer while timer is still empty (the assignment happens only after createTimer(...) returns). As a result, weakTimer.lock() will always fail and the callback never actually calls cancel(). Also, since this is a ONE_SHOT timer, timer->isActive() will typically become false even without cancellation. To truly test "cancel from the same thread", arrange for the callback to reliably call cancel() on the running timer (e.g., by capturing a raw pointer/reference that is valid for the timer lifetime, or by setting up the weak_ptr after construction in a synchronized way) and assert that the cancel path is hit.
9f691e9 to
5b42260
Compare
5b42260 to
164868e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| TEST_F(ActiveRequestsTests, insertShouldHandleDuplicateId) | ||
| { | ||
| EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits<std::uint32_t>::max())); | ||
| EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(1)); | ||
|
|
||
| EXPECT_EQ(std::numeric_limits<std::uint32_t>::max(), | ||
| m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits<std::uint32_t>::max())); | ||
| EXPECT_EQ(firebolt::rialto::MediaSourceType::VIDEO, m_sut.getType(std::numeric_limits<std::uint32_t>::max())); | ||
|
|
||
| auto newId = m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits<std::uint32_t>::max()); | ||
|
|
||
| EXPECT_NE(newId, 1u); | ||
| EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(newId)); | ||
| } |
There was a problem hiding this comment.
The new test assumes the first generated request id is 1 and that a subsequent insert can immediately return UINT32_MAX, but ActiveRequests currently starts m_currentId at 0 and increments sequentially. As written, this test will fail (first insert returns 0), and it also can’t realistically reach UINT32_MAX without manipulating m_currentId or inserting ~4B entries. Consider rewriting the test to match the current id semantics (starting at 0) or add a test hook to set m_currentId near UINT32_MAX if you specifically want to validate wrap/duplicate handling.
| TimerType::ONE_SHOT); | ||
|
|
||
| std::unique_lock<std::mutex> lock{mtx}; | ||
| cv.wait_for(lock, std::chrono::milliseconds{200}, [&] { return cancelled; }); |
There was a problem hiding this comment.
This test uses a hard-coded 200ms timeout for wait_for, but the file already defines kEnoughTimeForTestToComplete specifically to avoid valgrind-related flakiness. Using the shared constant here would keep timing consistent with the other timer tests and reduce the chance of sporadic failures in slower environments.
| cv.wait_for(lock, std::chrono::milliseconds{200}, [&] { return cancelled; }); | |
| cv.wait_for(lock, kEnoughTimeForTestToComplete, [&] { return cancelled; }); |
| m_active = false; | ||
| m_cv.notify_one(); | ||
|
|
||
| if (std::this_thread::get_id() == m_thread.get_id()) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (std::this_thread::get_id() != m_thread.get_id() && m_thread.joinable()) | ||
| if (m_thread.joinable()) | ||
| { | ||
| m_cv.notify_one(); | ||
| m_thread.join(); | ||
| } |
There was a problem hiding this comment.
Timer::cancel() returns early when called from the timer thread, leaving m_thread joinable. If the last owning reference to Timer is released on the timer thread (e.g., inside the callback), ~Timer() will call cancel() and hit this early-return path, and then the std::thread destructor will run while joinable -> std::terminate. To make same-thread cancellation safe, ensure the thread is not joinable in this case (e.g., detach, or move the join responsibility to another thread via a shared state).
| ActiveRequests::ActiveRequestsData::~ActiveRequestsData() | ||
| { | ||
| for (std::unique_ptr<IMediaPipeline::MediaSegment> &segment : m_segments) | ||
| { | ||
| delete[] segment->getData(); | ||
| } | ||
| RIALTO_SERVER_LOG_DEBUG("ActiveRequestsData destructor called"); | ||
| } |
There was a problem hiding this comment.
ActiveRequestsData’s destructor now only logs. Because this object is created/erased per need-data request, this debug log is likely to be extremely chatty and can add overhead/noise without providing actionable info. Consider removing the log (and defaulting the destructor) or, if this is temporary instrumentation, gating it behind a more targeted debug flag / including identifying context (e.g., request type/id) so it’s useful.
Summary: Adding new test to fix coverage %
Type: Fix
Test Plan: UT/CT, Fullstack
Jira: RDKEMW-15495
164868e to
24986b3
Compare
|
Coverage statistics of your commit: |
Fix memory leaks in Rialto master
Summary: Finding potential memory leak points and adding fixes
Type: Fix
Test Plan: UT/CT, Fullstack
Jira: RDKEMW-15495