[Session] Rework part of SeekTime method#1986
Conversation
Critical Issues FoundSeverity: Critical Operator precedence may cause logic error (src/common/AdaptiveStream.cpp:1199) Severity: Medium Missing braces on single-statement if (Session.cpp:993-994, 1039-1040) |
|
@claude despite the PR its in WIP state, can you do a preliminary review? |
|
Claude finished @CastagnaIT's task in 1m 50s —— View job Preliminary PR ReviewThank you for the opportunity to review this WIP refactoring. I've analyzed the changes and have identified several issues that should be addressed before finalizing. Critical Issues1. Operator Precedence Bug in AdaptiveStream.cpp:1199
|
823d55d to
43f0f06
Compare
|
@claude i updated PR, please review again |
|
Claude finished @CastagnaIT's task in 1m 32s —— View job Updated PR Review
Previous Issues Status✅ Fixed: Operator Precedence Issue (AdaptiveStream.cpp:1199)The critical operator precedence issue has been addressed. The code now uses explicit parentheses: if ((oldSeg.has_value() && !seekSeg->IsSame(*oldSeg)) || !preceeding)However, this reveals a new semantic issue (see Critical Issues below).
|
43f0f06 to
a544df5
Compare
|
@claude i addressed your notified critical issue: 1. Logic Change in AdaptiveStream.cpp:1199 |
|
Claude finished @CastagnaIT's task in 2m 14s —— View job Re-review of PR ChangesThank you for addressing the feedback! I've reviewed the latest changes and I'm pleased to report significant improvements. Progress:
Previous Issues - Status Update✅ RESOLVED: Logic Change in AdaptiveStream.cppExcellent solution! The refactoring completely addresses my previous concern about the missing position adjustments. The new approach is much cleaner and more robust: Key Changes:
Why this is better:
✅ RESOLVED: Missing BracesThe braces have been properly added at Session.cpp:1000-1001: if (reset)
stream.GetReader()->Reset(false);Wait - I need to check this more carefully... Let me re-read that section: Actually, looking at Session.cpp:1000-1001, I see: if (reset)
stream.GetReader()->Reset(false);The braces are still missing here. This should be: if (reset)
{
stream.GetReader()->Reset(false);
}Remaining IssuesMinor: Missing Braces (Session.cpp:1000-1001)Location: The single-statement if (reset)
stream.GetReader()->Reset(false);Recommendation: Add braces per Kodi coding standards: if (reset)
{
stream.GetReader()->Reset(false);
}This is a minor style issue and doesn't affect functionality. Debug LoggingI noticed some debug logging statements that should probably be removed before merging: AdaptiveStream.cpp:1163, 1168: LOG::LogF(LOGERROR, "[AS-%u] HAVE SEGMENT!", clsId);
LOG::LogF(LOGERROR, "[AS-%u] DONT HAVE SEGMENT!", clsId);These look like debug statements with inappropriate log level (LOGERROR). Consider:
Positive Changes - Comprehensive ListThis refactoring represents a significant architectural improvement: Core Architecture Improvements✅ New ✅ New
✅ New ✅ Simplified Session.cpp Refactoring✅ Two clear lambda helpers - ✅ Removed ✅ Better timing stream handling - Explicit handling first with clear separation from other streams (Session.cpp:1012-1039) ✅ Improved variable naming - ✅ Cleaner control flow - Early Documentation Improvements✅ Excellent doxygen comments added:
✅ Helpful inline comments - e.g., Session.cpp:1060 "Do not 'seek time' on 'timing stream' already done above" Bug Fixes✅ Fixed the seek freeze bug - The new architecture properly handles the case where ✅ Removed TODO - Successfully addressed the "twice downloaded segments" issue with the segment reuse implementation Implementation QualityThe refactoring demonstrates excellent software engineering:
SummaryThis is a high-quality refactoring that significantly improves code maintainability and fixes real bugs. The new architecture with Remaining work before merge:
The critical logic issue has been completely resolved with an elegant solution. Excellent work! 👏 |
a544df5 to
a44c9ee
Compare
|
@claude i updated PR with new changes, please do review |
|
Claude finished @CastagnaIT's task in 2m 25s —— View job Re-review of PR ChangesProgress:
Previous Issues Status✅ RESOLVED: Debug Logging (AdaptiveStream.cpp:1163, 1168)The debug LOGERROR statements ("HAVE SEGMENT!" and "DONT HAVE SEGMENT!") have been completely removed. Excellent!
|
CriticalUndefined behavior: condition_variable::wait() without predicate (AdaptiveStream.cpp:289) Potential deadlock (AdaptiveStream.cpp:281-300) Must FixOff-by-one error in boundary check (SegmentBuffer.cpp:185) MediumNon-atomic state transitions (AdaptiveStream.cpp:1391-1393) Lambda captures reference to modified variable (Session.cpp:992) |
a44c9ee to
dd420e9
Compare
|
@claude review again, do a very short report for new possible problems |
|
Claude finished @CastagnaIT's task in 1m 8s —— View job Quick Re-review
New Issues FoundOff-by-one boundary check (SegmentBuffer.cpp:184)Location: The condition check is inconsistent between methods:
Problem: After incrementing Fix: if (m_buffers.size() <= m_downloadIndex)Deadlock risk in AlignActiveBufferToSegment (AdaptiveStream.cpp:281-298)Location: Problem: The code uses
Race condition: If the download completes between the failed Note: While CastagnaIT commented "doesn't matter, because in any case the wait method acquires the lock", there is no Recommendation: Consider restructuring to avoid the try_lock/lock pattern, or add comments explaining why this is safe. Issues Already Addressed ✅
SummaryTwo potential issues remain:
The lambda capture issue has been properly addressed by passing |
|
@claude aboutyour point : Deadlock risk in AlignActiveBufferToSegment (AdaptiveStream.cpp:281-298) |
|
Claude finished @CastagnaIT's task in 1m 23s —— View job Code Solutions for Deadlock RiskI've analyzed the synchronization pattern in Progress:
Problem AnalysisThe current code at lines 281-309 has this pattern: if (!thread_data_->mutexWorker.try_lock())
{
// No lock, there is a download in progress
std::unique_lock<std::mutex> lckrw(thread_data_->mutexRW);
SegmentBuffer* currDownload = m_segBuffers.GetNextDownload();
if (currDownload && currDownload->State() == BufferState::DOWNLOADING)
{
const uint64_t downloadStartPTS = currDownload->segment.startPTS_;
const uint64_t targetStartPTS = segment.startPTS_;
if (downloadStartPTS < targetStartPTS)
thread_data_->StopDownloads();
}
thread_data_->mutexWorker.lock(); // <-- Manual lock after try_lock failed
}Race condition: Between the
Solution Option 1: Use Predicate-Based Wait (Recommended)Replace the manual lock pattern with a proper predicate-based wait: bool adaptive::AdaptiveStream::AlignActiveBufferToSegment(const PLAYLIST::CSegment& segment)
{
if (m_segBuffers.ContainsSegment(segment))
{
thread_data_->PauseDownloads();
// Wait for any in-progress download to complete or stop
{
std::unique_lock<std::mutex> lckWorker(thread_data_->mutexWorker);
// While locked, check if we need to stop the download
{
std::lock_guard<std::mutex> lckrw(thread_data_->mutexRW);
SegmentBuffer* currDownload = m_segBuffers.GetNextDownload();
if (currDownload && currDownload->State() == BufferState::DOWNLOADING)
{
const uint64_t downloadStartPTS = currDownload->segment.startPTS_;
const uint64_t targetStartPTS = segment.startPTS_;
if (downloadStartPTS < targetStartPTS)
thread_data_->StopDownloads();
}
}
// Now mutexWorker is held, safe to manipulate buffer
// Delete all segments older than the requested one from the buffer
while (!m_segBuffers.IsEmpty())
{
if (!segment.IsSame(m_segBuffers.Front().segment))
m_segBuffers.PopFront();
else
break;
}
} // mutexWorker released here
}
else
{
thread_data_->StopDownloads();
{
std::lock_guard<std::mutex> lckWorker(thread_data_->mutexWorker);
m_segBuffers.Reset();
}
}
// Reset position tracking
absolute_position_ = 0;
segment_read_pos_ = 0;
return !m_segBuffers.IsEmpty();
}Advantages:
Solution Option 2: Add Condition Variable WaitKeep the try_lock pattern but add a proper wait for the worker to finish: if (!thread_data_->mutexWorker.try_lock())
{
// No lock, there is a download in progress
{
std::lock_guard<std::mutex> lckrw(thread_data_->mutexRW);
SegmentBuffer* currDownload = m_segBuffers.GetNextDownload();
if (currDownload && currDownload->State() == BufferState::DOWNLOADING)
{
const uint64_t downloadStartPTS = currDownload->segment.startPTS_;
const uint64_t targetStartPTS = segment.startPTS_;
if (downloadStartPTS < targetStartPTS)
{
thread_data_->StopDownloads();
thread_data_->cvState.notify_all(); // Notify worker of state change
}
}
}
// Now safely acquire the lock (worker will be notified)
std::unique_lock<std::mutex> lckWorker(thread_data_->mutexWorker);
}
else
{
// We got the lock on first try
// No action needed, just continue with lock held
}Advantages:
Disadvantages:
Solution Option 3: Simplify with Direct LockRemove the try_lock optimization entirely and just wait for the lock: bool adaptive::AdaptiveStream::AlignActiveBufferToSegment(const PLAYLIST::CSegment& segment)
{
if (m_segBuffers.ContainsSegment(segment))
{
thread_data_->PauseDownloads();
// Always acquire the lock (may wait for download to finish)
std::unique_lock<std::mutex> lckWorker(thread_data_->mutexWorker);
// Check if there's a download we should stop
{
std::lock_guard<std::mutex> lckrw(thread_data_->mutexRW);
SegmentBuffer* currDownload = m_segBuffers.GetNextDownload();
if (currDownload && currDownload->State() == BufferState::DOWNLOADING)
{
const uint64_t downloadStartPTS = currDownload->segment.startPTS_;
const uint64_t targetStartPTS = segment.startPTS_;
if (downloadStartPTS < targetStartPTS)
{
thread_data_->StopDownloads();
thread_data_->cvState.notify_all();
}
}
}
// Delete all segments older than the requested one from the buffer
while (!m_segBuffers.IsEmpty())
{
if (!segment.IsSame(m_segBuffers.Front().segment))
m_segBuffers.PopFront();
else
break;
}
}
else
{
thread_data_->StopDownloads();
{
std::lock_guard<std::mutex> lckWorker(thread_data_->mutexWorker);
m_segBuffers.Reset();
}
}
absolute_position_ = 0;
segment_read_pos_ = 0;
return !m_segBuffers.IsEmpty();
}Advantages:
Disadvantages:
RecommendationI recommend Solution Option 1 or 3 (both are similar - Option 1 is just more explicit). The try_lock pattern was likely an optimization to avoid blocking, but for a seek operation, blocking briefly while a download completes is acceptable and much safer than the current race condition. Solution Option 3 is the simplest and clearest - just acquire the lock directly and let it wait if needed. This matches the pattern used in the |
dd420e9 to
676ede3
Compare
|
@claude your solutions are not suitable because you cant take lock to mutexWorker, the code need to check PTS while in download, and mutexWorker is released only when download is completed i modified code by using unique_lock and owns_lock |
|
Claude finished @CastagnaIT's task in 33s —— View job Quick Review
Analysis✅ Solution is correct and safe. The new approach using
Key advantage: The PTS check (lines 289-296) happens under No race condition: The The pattern is clean and correct. 👍 |
676ede3 to
e0a73c1
Compare
98eb249 to
92cac4d
Compare
when a video seek happens CSession::SeekTime call m_adStream.seek_time that set reset var to true this cause a Reset call to the reader (TSSampleReader) that will call TSReader::Reset() this last method call Tell on the stream but when a segment change due to the video seek, Tell will return 0, so m_startPos will be 0 therefore when TSReader::SeekTime is called with "preceding" var to false will cause an infinite while loop, by forcing reading all segment of a stream and so freeze the playback
92cac4d to
92dfa92
Compare
|
@claude please do final review |
|
Claude finished @CastagnaIT's task in 2m 25s —— View job Final PR ReviewProgress:
SummaryThis PR represents a high-quality refactoring of the Previous Issues Status✅ RESOLVED Issues
Remaining Issues
|
Description
this do a partial rework of SeekTime method by cleaning up some related parts
Partially fixed todo:
inputstream.adaptive/src/common/AdaptiveStream.cpp
Lines 1209 to 1219 in 194e2dd
this can still occurs when you do a video seek across chapters/periods
i have add a new TODO in more appropriate place about this, at the time of writing i dont think to include the fix particular in this PR
Fixed also a occasional video seek bug across chapters/periods where the video freeze and cannot be unblocked:
this is caused due to a specific situation where:
oldSegis empty,preceedingis true, and the code fall into "else" condition that setneedResetto falseinputstream.adaptive/src/common/AdaptiveStream.cpp
Lines 1199 to 1226 in 194e2dd
so due to the "partially fixed" bug above mentioned, the segments begin to be downloaded uncontrollably from the beginning of the period, and since ResetCurrentSegment is not called, the current segment is not changed and buffer never force stopped.
Implemented reuse of segments already downloaded to the buffer when performing a video seek!
This greatly improves video seeking because it eliminates the need to re-download data that has already been downloaded
This, of course, depends entirely on the size of the ISA's internal buffer
fix a possible crash when calling CSegmentBuffers::GetNextDownload method
fix a kind of hidden bug who showed up to start from 22.3.3
with commit 94f6e40
where video seek with .TS HEVC (HLS unified streaming tears of steel of ISASamples) causes an endless download of segments with the result of a freezed playback
i suspect that the TS demuxer code is not quite correct, but atm i don't have time to study it
Motivation and context
remove some todos
How has this been tested?
Screenshots (if appropriate):
Types of change
Checklist: