2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
11 #include "CircularCache.h"
12 #include "ServiceBroker.h"
14 #include "settings/AdvancedSettings.h"
15 #include "settings/SettingsComponent.h"
16 #include "threads/Thread.h"
17 #include "utils/log.h"
21 #if !defined(TARGET_WINDOWS)
22 #include "platform/posix/ConvUtils.h"
32 #include "platform/posix/ConvUtils.h"
35 using namespace XFILE
;
36 using namespace std::chrono_literals
;
43 m_stamp
= std::chrono::steady_clock::now();
46 m_time
= std::chrono::milliseconds(0);
49 void Reset(int64_t pos
, bool bResetAll
= true)
51 m_stamp
= std::chrono::steady_clock::now();
57 m_time
= std::chrono::milliseconds(0);
61 uint32_t Rate(int64_t pos
, uint32_t time_bias
= 0)
63 auto ts
= std::chrono::steady_clock::now();
65 m_size
+= (pos
- m_pos
);
66 m_time
+= std::chrono::duration_cast
<std::chrono::milliseconds
>(ts
- m_stamp
);
70 if (m_time
== std::chrono::milliseconds(0))
73 return static_cast<uint32_t>(1000 * (m_size
/ (m_time
.count() + time_bias
)));
77 std::chrono::time_point
<std::chrono::steady_clock
> m_stamp
;
79 std::chrono::milliseconds m_time
;
84 CFileCache::CFileCache(const unsigned int flags
)
85 : CThread("FileCache"),
94 m_writeRateLowSpeed(0),
95 m_forwardCacheSize(0),
102 CFileCache::~CFileCache()
107 IFile
*CFileCache::GetFileImp()
109 return m_source
.GetImplementation();
112 bool CFileCache::Open(const CURL
& url
)
116 std::unique_lock
<CCriticalSection
> lock(m_sync
);
118 m_sourcePath
= url
.GetRedacted();
120 CLog::Log(LOGDEBUG
, "CFileCache::{} - <{}> opening", __FUNCTION__
, m_sourcePath
);
122 // opening the source file.
123 if (!m_source
.Open(url
.Get(), READ_NO_CACHE
| READ_TRUNCATED
| READ_CHUNKED
))
125 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> failed to open", __FUNCTION__
, m_sourcePath
);
130 m_source
.IoControl(IOCTRL_SET_CACHE
, this);
133 m_source
.IoControl(IOCTRL_SET_RETRY
, &retry
); // We already handle retrying ourselves
135 // check if source can seek
136 m_seekPossible
= m_source
.IoControl(IOCTRL_SEEK_POSSIBLE
, NULL
);
138 // Determine the best chunk size we can use
139 m_chunkSize
= CFile::DetermineChunkSize(
140 m_source
.GetChunkSize(),
141 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheChunkSize
);
143 "CFileCache::{} - <{}> source chunk size is {}, setting cache chunk size to {}",
144 __FUNCTION__
, m_sourcePath
, m_source
.GetChunkSize(), m_chunkSize
);
146 m_fileSize
= m_source
.GetLength();
150 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheMemSize
== 0)
153 m_pCache
= std::unique_ptr
<CSimpleFileCache
>(new CSimpleFileCache()); // C++14 - Replace with std::make_unique
154 m_forwardCacheSize
= 0;
159 if (m_fileSize
> 0 && m_fileSize
< CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheMemSize
&& !(m_flags
& READ_AUDIO_VIDEO
))
161 // Cap cache size by filesize, but not for audio/video files as those may grow.
162 // We don't need to take into account READ_MULTI_STREAM here as that's only used for audio/video
163 cacheSize
= m_fileSize
;
165 // Cap chunk size by cache size
166 if (m_chunkSize
> cacheSize
)
167 m_chunkSize
= cacheSize
;
171 cacheSize
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheMemSize
;
173 // NOTE: READ_MULTI_STREAM is only used with READ_AUDIO_VIDEO
174 if (m_flags
& READ_MULTI_STREAM
)
176 // READ_MULTI_STREAM requires double buffering, so use half the amount of memory for each buffer
180 // Make sure cache can at least hold 2 chunks
181 if (cacheSize
< m_chunkSize
* 2)
182 cacheSize
= m_chunkSize
* 2;
185 if (m_flags
& READ_MULTI_STREAM
)
186 CLog::Log(LOGDEBUG
, "CFileCache::{} - <{}> using double memory cache each sized {} bytes",
187 __FUNCTION__
, m_sourcePath
, cacheSize
);
189 CLog::Log(LOGDEBUG
, "CFileCache::{} - <{}> using single memory cache sized {} bytes",
190 __FUNCTION__
, m_sourcePath
, cacheSize
);
192 const size_t back
= cacheSize
/ 4;
193 const size_t front
= cacheSize
- back
;
195 m_pCache
= std::unique_ptr
<CCircularCache
>(new CCircularCache(front
, back
)); // C++14 - Replace with std::make_unique
196 m_forwardCacheSize
= front
;
199 if (m_flags
& READ_MULTI_STREAM
)
201 // If READ_MULTI_STREAM flag is set: Double buffering is required
202 m_pCache
= std::unique_ptr
<CDoubleCache
>(new CDoubleCache(m_pCache
.release())); // C++14 - Replace with std::make_unique
206 // open cache strategy
207 if (!m_pCache
|| m_pCache
->Open() != CACHE_RC_OK
)
209 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> failed to open cache", __FUNCTION__
, m_sourcePath
);
216 m_writeRate
= 1024 * 1024;
217 m_writeRateActual
= 0;
218 m_writeRateLowSpeed
= 0;
223 CThread::Create(false);
228 void CFileCache::Process()
232 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> sanity failed. no cache strategy", __FUNCTION__
,
237 // create our read buffer
238 std::unique_ptr
<char[]> buffer(new char[m_chunkSize
]);
239 if (buffer
== nullptr)
241 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> failed to allocate read buffer", __FUNCTION__
,
252 m_fileSize
= m_source
.GetLength();
254 // check for seek events
255 if (m_seekEvent
.Wait(0ms
))
258 const int64_t cacheMaxPos
= m_pCache
->CachedDataEndPosIfSeekTo(m_seekPos
);
259 const bool cacheReachEOF
= (cacheMaxPos
== m_fileSize
);
261 bool sourceSeekFailed
= false;
264 m_nSeekResult
= m_source
.Seek(cacheMaxPos
, SEEK_SET
);
265 if (m_nSeekResult
!= cacheMaxPos
)
267 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> error {} seeking. Seek returned {}",
268 __FUNCTION__
, m_sourcePath
, GetLastError(), m_nSeekResult
);
269 m_seekPossible
= m_source
.IoControl(IOCTRL_SEEK_POSSIBLE
, NULL
);
270 sourceSeekFailed
= true;
274 if (!sourceSeekFailed
)
276 const bool bCompleteReset
= m_pCache
->Reset(m_seekPos
);
277 m_readPos
= m_seekPos
;
278 m_writePos
= m_pCache
->CachedDataEndPos();
279 assert(m_writePos
== cacheMaxPos
);
280 average
.Reset(m_writePos
, bCompleteReset
); // Can only recalculate new average from scratch after a full reset (empty cache)
281 limiter
.Reset(m_writePos
);
282 m_nSeekResult
= m_seekPos
;
286 "CFileCache::{} - <{}> cache completely reset for seek to position {}",
287 __FUNCTION__
, m_sourcePath
, m_seekPos
);
289 m_writeRateLowSpeed
= 0;
298 if (m_writePos
- m_readPos
< m_writeRate
* CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheReadFactor
)
300 limiter
.Reset(m_writePos
);
304 if (limiter
.Rate(m_writePos
) < m_writeRate
* CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheReadFactor
)
307 if (m_seekEvent
.Wait(100ms
))
315 const int64_t maxWrite
= m_pCache
->GetMaxWriteSize(m_chunkSize
);
316 int64_t maxSourceRead
= m_chunkSize
;
317 // Cap source read size by space available between current write position and EOF
319 maxSourceRead
= std::min(maxSourceRead
, m_fileSize
- m_writePos
);
321 /* Only read from source if there's enough write space in the cache
322 * else we may keep disposing data and seeking back on (slow) source
324 if (maxWrite
< maxSourceRead
)
326 // Wait until sufficient cache write space is available
327 m_pCache
->m_space
.Wait(5ms
);
332 if (maxSourceRead
> 0)
333 iRead
= m_source
.Read(buffer
.get(), maxSourceRead
);
336 // Check for actual EOF and retry as long as we still have data in our cache
337 if (m_writePos
< m_fileSize
&& m_pCache
->WaitForData(0, 0ms
) > 0)
339 CLog::Log(LOGWARNING
, "CFileCache::{} - <{}> source read returned {}! Will retry",
340 __FUNCTION__
, m_sourcePath
, iRead
);
343 if (m_seekEvent
.Wait(2000ms
))
346 m_seekEvent
.Set(); // hack so that later we realize seek is needed
350 continue; // while (!m_bStop)
356 "{} - <{}> source read failed with {}!", __FUNCTION__
, m_sourcePath
, iRead
);
357 else if (m_fileSize
== 0)
359 "CFileCache::{} - <{}> source read didn't return any data! Hit eof(?)",
360 __FUNCTION__
, m_sourcePath
);
361 else if (m_writePos
< m_fileSize
)
363 "CFileCache::{} - <{}> source read didn't return any data before eof!",
364 __FUNCTION__
, m_sourcePath
);
366 CLog::Log(LOGDEBUG
, "CFileCache::{} - <{}> source read hit eof", __FUNCTION__
,
369 m_pCache
->EndOfInput();
371 // The thread event will now also cause the wait of an event to return a false.
372 if (AbortableWait(m_seekEvent
) == WAIT_SIGNALED
)
374 m_pCache
->ClearEndOfInput();
376 m_seekEvent
.Set(); // hack so that later we realize seek is needed
379 break; // while (!m_bStop)
384 while (!m_bStop
&& (iTotalWrite
< iRead
))
387 iWrite
= m_pCache
->WriteToCache(buffer
.get() + iTotalWrite
, iRead
- iTotalWrite
);
389 // write should always work. all handling of buffering and errors should be
390 // done inside the cache strategy. only if unrecoverable error happened, WriteToCache would return error and we break.
393 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> error writing to cache", __FUNCTION__
,
398 else if (iWrite
== 0)
400 m_pCache
->m_space
.Wait(5ms
);
403 iTotalWrite
+= iWrite
;
405 // check if seek was asked. otherwise if cache is full we'll freeze.
406 if (m_seekEvent
.Wait(0ms
))
409 m_seekEvent
.Set(); // make sure we get the seek event later.
414 m_writePos
+= iTotalWrite
;
416 // under estimate write rate by a second, to
417 // avoid uncertainty at start of caching
418 m_writeRateActual
= average
.Rate(m_writePos
, 1000);
420 /* NOTE: We can only reliably test for low speed condition, when the cache is *really*
421 * filling. This is because as soon as it's full the average-
422 * rate will become approximately the current-rate which can flag false
423 * low read-rate conditions.
425 if (m_bFilling
&& m_forwardCacheSize
!= 0)
427 const int64_t forward
= m_pCache
->WaitForData(0, 0ms
);
428 if (forward
+ m_chunkSize
>= m_forwardCacheSize
)
430 if (m_writeRateActual
< m_writeRate
)
431 m_writeRateLowSpeed
= m_writeRateActual
;
439 void CFileCache::OnExit()
443 // make sure cache is set to mark end of file (read may be waiting).
445 m_pCache
->EndOfInput();
447 // just in case someone's waiting...
451 bool CFileCache::Exists(const CURL
& url
)
453 return CFile::Exists(url
.Get());
456 int CFileCache::Stat(const CURL
& url
, struct __stat64
* buffer
)
458 return CFile::Stat(url
.Get(), buffer
);
461 ssize_t
CFileCache::Read(void* lpBuf
, size_t uiBufSize
)
463 std::unique_lock
<CCriticalSection
> lock(m_sync
);
466 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> sanity failed. no cache strategy!", __FUNCTION__
,
472 if (uiBufSize
> SSIZE_MAX
)
473 uiBufSize
= SSIZE_MAX
;
477 iRc
= m_pCache
->ReadFromCache((char *)lpBuf
, uiBufSize
);
484 if (iRc
== CACHE_RC_WOULD_BLOCK
)
486 // just wait for some data to show up
487 iRc
= m_pCache
->WaitForData(1, 10s
);
492 if (iRc
== CACHE_RC_TIMEOUT
)
494 CLog::Log(LOGWARNING
, "CFileCache::{} - <{}> timeout waiting for data", __FUNCTION__
,
502 // unknown error code
503 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> cache strategy returned unknown error code {}",
504 __FUNCTION__
, m_sourcePath
, (int)iRc
);
508 int64_t CFileCache::Seek(int64_t iFilePosition
, int iWhence
)
510 std::unique_lock
<CCriticalSection
> lock(m_sync
);
514 CLog::Log(LOGERROR
, "CFileCache::{} - <{}> sanity failed. no cache strategy!", __FUNCTION__
,
519 int64_t iCurPos
= m_readPos
;
520 int64_t iTarget
= iFilePosition
;
521 if (iWhence
== SEEK_END
)
522 iTarget
= m_fileSize
+ iTarget
;
523 else if (iWhence
== SEEK_CUR
)
524 iTarget
= iCurPos
+ iTarget
;
525 else if (iWhence
!= SEEK_SET
)
528 if (iTarget
== m_readPos
)
531 if ((m_nSeekResult
= m_pCache
->Seek(iTarget
)) != iTarget
)
533 if (m_seekPossible
== 0)
534 return m_nSeekResult
;
536 // Never request closer to end than one chunk. Speeds up tag reading
537 m_seekPos
= std::min(iTarget
, std::max((int64_t)0, m_fileSize
- m_chunkSize
));
540 while (!m_seekEnded
.Wait(100ms
))
542 // SeekEnded will never be set if FileCache thread is not running
543 if (!CThread::IsRunning())
547 /* wait for any remaining data */
548 if(m_seekPos
< iTarget
)
550 CLog::Log(LOGDEBUG
, "CFileCache::{} - <{}> waiting for position {}", __FUNCTION__
,
551 m_sourcePath
, iTarget
);
552 if (m_pCache
->WaitForData(static_cast<uint32_t>(iTarget
- m_seekPos
), 10s
) <
555 CLog::Log(LOGWARNING
, "CFileCache::{} - <{}> failed to get remaining data", __FUNCTION__
,
559 m_pCache
->Seek(iTarget
);
570 void CFileCache::Close()
574 std::unique_lock
<CCriticalSection
> lock(m_sync
);
581 int64_t CFileCache::GetPosition()
586 int64_t CFileCache::GetLength()
591 void CFileCache::StopThread(bool bWait
/*= true*/)
594 //Process could be waiting for seekEvent
596 CThread::StopThread(bWait
);
599 const std::string
CFileCache::GetProperty(XFILE::FileProperty type
, const std::string
&name
) const
601 if (!m_source
.GetImplementation())
602 return IFile::GetProperty(type
, name
);
604 return m_source
.GetImplementation()->GetProperty(type
, name
);
607 int CFileCache::IoControl(EIoControl request
, void* param
)
609 if (request
== IOCTRL_CACHE_STATUS
)
611 SCacheStatus
* status
= (SCacheStatus
*)param
;
612 status
->forward
= m_pCache
->WaitForData(0, 0ms
);
613 status
->maxrate
= m_writeRate
;
614 status
->currate
= m_writeRateActual
;
615 status
->lowrate
= m_writeRateLowSpeed
;
616 m_writeRateLowSpeed
= 0; // Reset low speed condition
620 if (request
== IOCTRL_CACHE_SETRATE
)
622 m_writeRate
= *static_cast<uint32_t*>(param
);
626 if (request
== IOCTRL_SEEK_POSSIBLE
)
627 return m_seekPossible
;