MythTV master
threadedfilewriter.cpp
Go to the documentation of this file.
1// C++ headers
2#include <cerrno>
3#include <csignal>
4#include <cstdio>
5#include <cstdlib>
6#include <cstring>
7#include <fcntl.h>
8#include <sys/stat.h>
9#include <sys/types.h>
10#include <unistd.h>
11
12// Qt headers
13#include <QtGlobal>
14#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
15#include <QtSystemDetection>
16#endif
17#include <QString>
18
19// MythTV headers
20#include "threadedfilewriter.h"
21#include "mythlogging.h"
22#include "mythcorecontext.h"
23
24#include "mythtimer.h"
25#include "compat.h"
26#include "mythdate.h"
27
28#define LOC QString("TFW(%1:%2): ").arg(m_filename).arg(m_fd)
29
32{
33 RunProlog();
35 RunEpilog();
36}
37
40{
41 RunProlog();
43 RunEpilog();
44}
45
46const uint ThreadedFileWriter::kMaxBufferSize = 8 * 1024 * 1024;
48const uint ThreadedFileWriter::kMaxBlockSize = 1 * 1024 * 1024;
49
67bool ThreadedFileWriter::ReOpen(const QString& newFilename)
68{
69 Flush();
70
71 m_bufLock.lock();
72
73 if (m_fd >= 0)
74 {
75 close(m_fd);
76 m_fd = -1;
77 }
78
79 if (m_registered)
80 {
82 }
83
84 if (!newFilename.isEmpty())
85 m_filename = newFilename;
86
87 m_bufLock.unlock();
88
89 return Open();
90}
91
97{
98 m_ignoreWrites = false;
99
100 if (m_filename == "-")
101 {
102 m_fd = fileno(stdout);
103 }
104 else
105 {
106 QByteArray fname = m_filename.toLocal8Bit();
107 m_fd = open(fname.constData(), m_flags, m_mode);
108 }
109
110 if (m_fd < 0)
111 {
112 LOG(VB_GENERAL, LOG_ERR, LOC +
113 QString("Opening file '%1'.").arg(m_filename) + ENO);
114 return false;
115 }
116
118 m_registered = true;
119
120 LOG(VB_FILE, LOG_INFO, LOC + "Open() successful");
121
122#ifdef Q_OS_WINDOWS
123 _setmode(m_fd, _O_BINARY);
124#endif
125 if (!m_writeThread)
126 {
127 m_writeThread = new TFWWriteThread(this);
129 }
130
131 if (!m_syncThread)
132 {
133 m_syncThread = new TFWSyncThread(this);
135 }
136
137 return true;
138}
139
144{
145 Flush();
146
147 { /* tell child threads to exit */
148 QMutexLocker locker(&m_bufLock);
149 m_inDtor = true;
150 m_bufferSyncWait.wakeAll();
151 m_bufferHasData.wakeAll();
152 }
153
154 if (m_writeThread)
155 {
157 delete m_writeThread;
158 m_writeThread = nullptr;
159 }
160
161 while (!m_writeBuffers.empty())
162 {
163 delete m_writeBuffers.front();
164 m_writeBuffers.pop_front();
165 }
166
167 while (!m_emptyBuffers.empty())
168 {
169 delete m_emptyBuffers.front();
170 m_emptyBuffers.pop_front();
171 }
172
173 if (m_syncThread)
174 {
176 delete m_syncThread;
177 m_syncThread = nullptr;
178 }
179
180 if (m_fd >= 0)
181 {
182 close(m_fd);
183 m_fd = -1;
184 }
185
187 m_registered = false;
188}
189
196int ThreadedFileWriter::Write(const void *data, uint count)
197{
198 if (count == 0)
199 return 0;
200
201 QMutexLocker locker(&m_bufLock);
202
203 if (m_ignoreWrites)
204 return -1;
205
206 uint written = 0;
207 uint left = count;
208
209 while (written < count)
210 {
211 uint towrite = (left > kMaxBlockSize) ? kMaxBlockSize : left;
212
213 if ((m_totalBufferUse + towrite) > (kMaxBufferSize * (m_blocking ? 1 : 8)))
214 {
215 if (!m_blocking)
216 {
217 LOG(VB_GENERAL, LOG_ERR, LOC +
218 "Maximum buffer size exceeded."
219 "\n\t\t\tfile will be truncated, no further writing "
220 "will be done."
221 "\n\t\t\tThis generally indicates your disk performance "
222 "\n\t\t\tis insufficient to deal with the number of on-going "
223 "\n\t\t\trecordings, or you have a disk failure.");
224 m_ignoreWrites = true;
225 return -1;
226 }
227 if (!m_warned)
228 {
229 LOG(VB_GENERAL, LOG_WARNING, LOC +
230 "Maximum buffer size exceeded."
231 "\n\t\t\tThis generally indicates your disk performance "
232 "\n\t\t\tis insufficient or you have a disk failure.");
233 m_warned = true;
234 }
235 // wait until some was written to disk, and try again
236 if (!m_bufferWasFreed.wait(locker.mutex(), 1000))
237 {
238 LOG(VB_GENERAL, LOG_DEBUG, LOC +
239 QString("Taking a long time waiting to write.. "
240 "buffer size %1 (needing %2, %3 to go)")
241 .arg(m_totalBufferUse).arg(towrite)
242 .arg(towrite-(kMaxBufferSize-m_totalBufferUse)));
243 }
244 continue;
245 }
246
247 TFWBuffer *buf = nullptr;
248
249 if (!m_writeBuffers.empty() &&
250 (m_writeBuffers.back()->data.size() + towrite) < kMinWriteSize)
251 {
252 buf = m_writeBuffers.back();
253 m_writeBuffers.pop_back();
254 }
255 else
256 {
257 if (!m_emptyBuffers.empty())
258 {
259 buf = m_emptyBuffers.front();
260 m_emptyBuffers.pop_front();
261 buf->data.clear();
262 }
263 else
264 {
265 buf = new TFWBuffer();
266 }
267 }
268
269 m_totalBufferUse += towrite;
270
271 const char *cdata = (const char*) data + written;
272 buf->data.insert(buf->data.end(), cdata, cdata+towrite);
274
275 m_writeBuffers.push_back(buf);
276
277 if ((m_writeBuffers.size() > 1) || (buf->data.size() >= kMinWriteSize))
278 {
279 m_bufferHasData.wakeAll();
280 }
281
282 written += towrite;
283 left -= towrite;
284 }
285
286 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Write(*, %1) total %2 cnt %3")
287 .arg(count,4).arg(m_totalBufferUse).arg(m_writeBuffers.size()));
288
289 return count;
290}
291
303long long ThreadedFileWriter::Seek(long long pos, int whence)
304{
305 QMutexLocker locker(&m_bufLock);
306 m_flush = true;
307 while (!m_writeBuffers.empty())
308 {
309 m_bufferHasData.wakeAll();
310 if (!m_bufferEmpty.wait(locker.mutex(), 2000))
311 {
312 LOG(VB_GENERAL, LOG_WARNING, LOC +
313 QString("Taking a long time to flush.. buffer size %1")
314 .arg(m_totalBufferUse));
315 }
316 }
317 m_flush = false;
318 return lseek(m_fd, pos, whence);
319}
320
325{
326 QMutexLocker locker(&m_bufLock);
327 m_flush = true;
328 while (!m_writeBuffers.empty())
329 {
330 m_bufferHasData.wakeAll();
331 if (!m_bufferEmpty.wait(locker.mutex(), 2000))
332 {
333 LOG(VB_GENERAL, LOG_WARNING, LOC +
334 QString("Taking a long time to flush.. buffer size %1")
335 .arg(m_totalBufferUse));
336 }
337 }
338 m_flush = false;
339}
340
362{
363 if (m_fd >= 0)
364 {
365#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0
366 // fdatasync tries to avoid updating metadata, but will in
367 // practice always update metadata if any data is written
368 // as the file will usually have grown.
369 fdatasync(m_fd);
370#else
371 fsync(m_fd);
372#endif
373 }
374}
375
381{
382 QMutexLocker locker(&m_bufLock);
383 if (newMinSize > 0)
384 m_tfwMinWriteSize = newMinSize;
385 m_bufferHasData.wakeAll();
386}
387
392{
393 QMutexLocker locker(&m_bufLock);
394 while (!m_inDtor)
395 {
396 locker.unlock();
397
398 Sync();
399
400 locker.relock();
401
403 {
404 // we aren't going to write to the disk anymore, so can de-register
406 m_registered = false;
407 }
408 m_bufferSyncWait.wait(&m_bufLock, 1000);
409 }
410}
411
416{
417#ifndef Q_OS_WINDOWS
418 // don't exit program if file gets larger than quota limit..
419 signal(SIGXFSZ, SIG_IGN);
420#endif
421
422 QMutexLocker locker(&m_bufLock);
423
424 // Even if the bytes buffered is less than the minimum write
425 // size we do want to write to the OS buffers periodically.
426 // This timer makes sure we do.
427 MythTimer minWriteTimer;
428 MythTimer lastRegisterTimer;
429 minWriteTimer.start();
430 lastRegisterTimer.start();
431
432 uint64_t total_written = 0LL;
433
434 while (!m_inDtor)
435 {
436 if (m_ignoreWrites)
437 {
438 while (!m_writeBuffers.empty())
439 {
440 delete m_writeBuffers.front();
441 m_writeBuffers.pop_front();
442 }
443 while (!m_emptyBuffers.empty())
444 {
445 delete m_emptyBuffers.front();
446 m_emptyBuffers.pop_front();
447 }
448 m_bufferEmpty.wakeAll();
449 m_bufferHasData.wait(locker.mutex());
450 continue;
451 }
452
453 if (m_writeBuffers.empty())
454 {
455 m_bufferEmpty.wakeAll();
456 m_bufferHasData.wait(locker.mutex(), 1000);
458 continue;
459 }
460
461 auto mwte = minWriteTimer.elapsed();
462 if (!m_flush && (mwte < 250ms) && (m_totalBufferUse < kMinWriteSize))
463 {
464 m_bufferHasData.wait(locker.mutex(), (250ms - mwte).count());
466 continue;
467 }
468
469 if (m_fd == -1)
470 {
471 m_bufferHasData.wait(locker.mutex(), 200);
473 continue;
474 }
475
476 TFWBuffer *buf = m_writeBuffers.front();
477 m_writeBuffers.pop_front();
478 m_totalBufferUse -= buf->data.size();
479 m_bufferWasFreed.wakeAll();
480 minWriteTimer.start();
481
483
484 const void *data = (buf->data).data();
485 uint sz = buf->data.size();
486
487 bool write_ok = true;
488 uint tot = 0;
489 uint errcnt = 0;
490
491 LOG(VB_FILE, LOG_DEBUG, LOC + QString("write(%1) cnt %2 total %3")
492 .arg(sz).arg(m_writeBuffers.size())
493 .arg(m_totalBufferUse));
494
495 MythTimer writeTimer;
496 writeTimer.start();
497
498 while ((tot < sz) && !m_inDtor)
499 {
500 locker.unlock();
501
502 int ret = write(m_fd, (char *)data + tot, sz - tot);
503
504 if (ret < 0)
505 {
506 if (errno == EAGAIN)
507 {
508 LOG(VB_GENERAL, LOG_WARNING, LOC + "Got EAGAIN.");
509 }
510 else
511 {
512 errcnt++;
513 LOG(VB_GENERAL, LOG_ERR, LOC + "File I/O " +
514 QString(" errcnt: %1").arg(errcnt) + ENO);
515 }
516
517 if ((errcnt >= 3) || (ENOSPC == errno) || (EFBIG == errno))
518 {
519 locker.relock();
520 write_ok = false;
521 break;
522 }
523 }
524 else
525 {
526 tot += ret;
527 total_written += ret;
528 LOG(VB_FILE, LOG_DEBUG, LOC +
529 QString("total written so far: %1 bytes")
530 .arg(total_written));
531 }
532
533 locker.relock();
534
535 if ((tot < sz) && !m_inDtor)
536 m_bufferHasData.wait(locker.mutex(), 50);
537 }
538
540
541 if (lastRegisterTimer.elapsed() >= 10s)
542 {
544 m_registered = true;
545 lastRegisterTimer.restart();
546 }
547
549 m_emptyBuffers.push_back(buf);
550
551 if (writeTimer.elapsed() > 1s)
552 {
553 LOG(VB_GENERAL, LOG_WARNING, LOC +
554 QString("write(%1) cnt %2 total %3 -- took a long time, %4 ms")
555 .arg(sz).arg(m_writeBuffers.size())
556 .arg(m_totalBufferUse).arg(writeTimer.elapsed().count()));
557 }
558
559 if (!write_ok && ((EFBIG == errno) || (ENOSPC == errno)))
560 {
561 QString msg;
562 switch (errno)
563 {
564 case EFBIG:
565 msg =
566 "Maximum file size exceeded by '%1'"
567 "\n\t\t\t"
568 "You must either change the process ulimits, configure"
569 "\n\t\t\t"
570 "your operating system with \"Large File\" support, "
571 "or use"
572 "\n\t\t\t"
573 "a filesystem which supports 64-bit or 128-bit files."
574 "\n\t\t\t"
575 "HINT: FAT32 is a 32-bit filesystem.";
576 break;
577 case ENOSPC:
578 msg =
579 "No space left on the device for file '%1'"
580 "\n\t\t\t"
581 "file will be truncated, no further writing "
582 "will be done.";
583 break;
584 }
585
586 LOG(VB_GENERAL, LOG_ERR, LOC + msg.arg(m_filename));
587 m_ignoreWrites = true;
588 }
589 }
590}
591
593{
594 QDateTime cur = MythDate::current();
595 QDateTime cur_m_60 = cur.addSecs(-60);
596
597 QList<TFWBuffer*>::iterator it = m_emptyBuffers.begin();
598 while (it != m_emptyBuffers.end())
599 {
600 if (((*it)->lastUsed < cur_m_60) ||
601 ((*it)->data.capacity() > 3 * (*it)->data.size() &&
602 (*it)->data.capacity() > 64 * 1024LL))
603 {
604 delete *it;
605 it = m_emptyBuffers.erase(it);
606 continue;
607 }
608 ++it;
609 }
610}
611
620{
621 bool old = m_blocking;
622 m_blocking = block;
623 return old;
624}
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:180
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:267
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:193
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:284
void RegisterFileForWrite(const QString &file, uint64_t size=0LL)
void UnregisterFileForWrite(const QString &file)
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
std::chrono::milliseconds restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
void run(void) override
Runs ThreadedFileWriter::SyncLoop(void)
ThreadedFileWriter * m_parent
void run(void) override
Runs ThreadedFileWriter::DiskLoop(void)
ThreadedFileWriter * m_parent
void DiskLoop(void)
The thread run method that actually calls writes to disk.
void SetWriteBufferMinWriteSize(uint newMinSize=kMinWriteSize)
Sets the minumum number of bytes to write to disk in a single write.
QWaitCondition m_bufferHasData
TFWWriteThread * m_writeThread
void Sync(void) const
Flush data written to the file descriptor to disk.
bool SetBlocking(bool block=true)
Set write blocking mode While in blocking mode, ThreadedFileWriter::Write will wait for buffers to be...
QWaitCondition m_bufferEmpty
long long Seek(long long pos, int whence)
Seek to a position within stream; May be unsafe.
static const uint kMaxBufferSize
friend class TFWSyncThread
bool Open(void)
Opens the file we will be writing to.
void Flush(void)
Allow DiskLoop() to flush buffer completely ignoring low watermark.
static const uint kMinWriteSize
Minimum to write to disk in a single write, when not flushing buffer.
friend class TFWWriteThread
QWaitCondition m_bufferWasFreed
int Write(const void *data, uint count)
Writes data to the end of the write buffer.
QList< TFWBuffer * > m_emptyBuffers
bool ReOpen(const QString &newFilename="")
Reopens the file we are writing to or opens a new file.
void SyncLoop(void)
The thread run method that calls Sync(void).
static const uint kMaxBlockSize
Maximum block size to write at a time.
QList< TFWBuffer * > m_writeBuffers
~ThreadedFileWriter()
Commits all writes and closes the file.
QWaitCondition m_bufferSyncWait
TFWSyncThread * m_syncThread
#define fsync(FD)
Definition: compat.h:56
unsigned int uint
Definition: compat.h:60
#define close
Definition: compat.h:28
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
def write(text, progress=True)
Definition: mythburn.py:306
#define LOC