File indexing completed on 2024-04-28 04:58:02

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0003     SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
0004 */
0005 
0006 #ifndef TRANSFER_H
0007 #define TRANSFER_H
0008 
0009 #include <QVarLengthArray>
0010 #include <QtGlobal>
0011 
0012 #include <array>
0013 #include <condition_variable>
0014 #include <mutex>
0015 
0016 constexpr off_t c_minSegmentSize = 64 * 1024; // minimal size on stack
0017 constexpr off_t c_maxSegmentSize = 4L * 1024 * 1024; // 4MiB is the largest request we make
0018 
0019 struct TransferSegment {
0020     explicit TransferSegment(const off_t fileSize);
0021 
0022     ssize_t size = 0; // current size (i.e. the size that was put into buf)
0023     QVarLengthArray<char, c_minSegmentSize> buf; // data buffer, only filled up to size!
0024 
0025 private:
0026     static off_t segmentSizeForFileSize(const off_t fileSize_);
0027 };
0028 
0029 // Naive ring buffer.
0030 // Segment instances are held in the buffer, i.e. only alloc'd once at
0031 // beginning of the operation. Kind of a mix between ring and pool.
0032 //
0033 // The popping thread cannot pop while the pushing thread is still on
0034 // an element. As such we need at least 3 elements to prevent dead locks.
0035 class TransferRingBuffer
0036 {
0037 public:
0038     // fileSize is the stat'd file size of the source file.
0039     explicit TransferRingBuffer(const off_t fileSize_);
0040     ~TransferRingBuffer() = default;
0041 
0042     // Pops an item into the pull thread. This blocks
0043     // when the push thread is also currently on that index.
0044     // This can return nullptr if the push thread set the done state.
0045     // @note once done unpop() needs calling
0046     TransferSegment *pop();
0047 
0048     // Frees the item used by the pull thread. So it may be used by the
0049     // push thread.
0050     void unpop();
0051 
0052     // Simply returns a ptr to the item the current push thread marker is
0053     // at. i.e. the item "locked" for reading.
0054     // @note once done push() needs calling
0055     TransferSegment *nextFree();
0056 
0057     // Pushes ahead from the item obtained by nextFree.
0058     // This effectively allows the pull thread to pop() this item again.
0059     void push();
0060 
0061     // Only called by push thread to mark the buffer done and wake waiting
0062     // threads.
0063     void done();
0064 
0065 private:
0066     bool m_done = false;
0067     std::mutex m_mutex;
0068     std::condition_variable m_cond;
0069     static const size_t m_capacity = 4;
0070     std::array<std::unique_ptr<TransferSegment>, m_capacity> m_buffer;
0071     size_t head = 0; // index of push thread (prevents pop() from pull thread)
0072     size_t tail = 0; // index of pull thread (prevents push() from push thread)
0073 };
0074 
0075 #endif // TRANSFER_H