File indexing completed on 2024-05-12 15:56:58

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  References:
0005  *    * Maged M. Michael, Safe memory reclamation for dynamic
0006  *      lock-free objects using atomic reads and writes,
0007  *      Proceedings of the twenty-first annual symposium on
0008  *      Principles of distributed computing, July 21-24, 2002,
0009  *      Monterey, California
0010  *
0011  *    * Idea of m_deleteBlockers is taken from Andrey Gulin's blog
0012  *      https://users.livejournal.com/-foreseer/34284.html
0013  *
0014  *  SPDX-License-Identifier: GPL-2.0-or-later
0015  */
0016 
0017 #ifndef __KIS_LOCKLESS_STACK_H
0018 #define __KIS_LOCKLESS_STACK_H
0019 
0020 #include <QAtomicPointer>
0021 
0022 template<class T>
0023 class KisLocklessStack
0024 {
0025 private:
0026     struct Node {
0027         Node *next;
0028         T data;
0029     };
0030 
0031 public:
0032     KisLocklessStack() { }
0033     ~KisLocklessStack() {
0034 
0035         freeList(m_top.fetchAndStoreOrdered(0));
0036         freeList(m_freeNodes.fetchAndStoreOrdered(0));
0037     }
0038 
0039     void push(T data) {
0040         Node *newNode = new Node();
0041         newNode->data = data;
0042 
0043         Node *top;
0044 
0045         do {
0046             top = m_top;
0047             newNode->next = top;
0048         } while (!m_top.testAndSetOrdered(top, newNode));
0049 
0050         m_numNodes.ref();
0051     }
0052 
0053     bool pop(T &value) {
0054         bool result = false;
0055 
0056         m_deleteBlockers.ref();
0057 
0058         while(1) {
0059             Node *top = (Node*) m_top;
0060             if(!top) break;
0061 
0062             // This is safe as we ref'ed m_deleteBlockers
0063             Node *next = top->next;
0064 
0065             if(m_top.testAndSetOrdered(top, next)) {
0066                 m_numNodes.deref();
0067                 result = true;
0068 
0069                 value = top->data;
0070 
0071                 /**
0072                  * Test if we are the only delete blocker left
0073                  * (it means that we are the only owner of 'top')
0074                  * If there is someone else in "delete-blocked section",
0075                  * then just add the struct to the list of free nodes.
0076                  */
0077                 if (m_deleteBlockers == 1) {
0078                     cleanUpNodes();
0079                     delete top;
0080                 }
0081                 else {
0082                     releaseNode(top);
0083                 }
0084 
0085                 break;
0086             }
0087         }
0088 
0089         m_deleteBlockers.deref();
0090 
0091         return result;
0092     }
0093 
0094     void clear() {
0095         // a fast-path without write ops
0096         if(!m_top) return;
0097 
0098         m_deleteBlockers.ref();
0099 
0100         Node *top = m_top.fetchAndStoreOrdered(0);
0101 
0102         int removedChunkSize = 0;
0103         Node *tmp = top;
0104         while(tmp) {
0105             removedChunkSize++;
0106             tmp = tmp->next;
0107         }
0108         m_numNodes.fetchAndAddOrdered(-removedChunkSize);
0109 
0110         while(top) {
0111             Node *next = top->next;
0112 
0113             if (m_deleteBlockers == 1) {
0114                 /**
0115                  * We  are the only owner of top contents.
0116                  * So we can delete it freely.
0117                  */
0118                 cleanUpNodes();
0119                 freeList(top);
0120                 next = 0;
0121             }
0122             else {
0123                 releaseNode(top);
0124             }
0125 
0126             top = next;
0127         }
0128 
0129         m_deleteBlockers.deref();
0130     }
0131 
0132     void mergeFrom(KisLocklessStack<T> &other) {
0133         Node *otherTop = other.m_top.fetchAndStoreOrdered(0);
0134         if (!otherTop) return;
0135 
0136         int removedChunkSize = 1;
0137         Node *last = otherTop;
0138         while(last->next) {
0139             removedChunkSize++;
0140             last = last->next;
0141         }
0142         other.m_numNodes.fetchAndAddOrdered(-removedChunkSize);
0143 
0144         Node *top;
0145 
0146         do {
0147             top = m_top;
0148             last->next = top;
0149         } while (!m_top.testAndSetOrdered(top, otherTop));
0150 
0151         m_numNodes.fetchAndAddOrdered(removedChunkSize);
0152     }
0153 
0154     /**
0155      * This is impossible to measure the size of the stack
0156      * in highly concurrent environment. So we return approximate
0157      * value! Do not rely on this value much!
0158      */
0159     qint32 size() const {
0160         return m_numNodes;
0161     }
0162 
0163     bool isEmpty() const {
0164         return !m_numNodes;
0165     }
0166 
0167 private:
0168 
0169     inline void releaseNode(Node *node) {
0170         Node *top;
0171         do {
0172             top = m_freeNodes;
0173             node->next = top;
0174         } while (!m_freeNodes.testAndSetOrdered(top, node));
0175     }
0176 
0177     inline void cleanUpNodes() {
0178         Node *cleanChain = m_freeNodes.fetchAndStoreOrdered(0);
0179         if (!cleanChain) return;
0180 
0181         /**
0182          * If we are the only users of the objects is cleanChain,
0183          * then just free it. Otherwise, push them back into the
0184          * recycling list and keep them there till another
0185          * chance comes.
0186          */
0187         if (m_deleteBlockers == 1) {
0188             freeList(cleanChain);
0189         } else {
0190             Node *last = cleanChain;
0191             while (last->next) last = last->next;
0192 
0193             Node *freeTop;
0194 
0195             do {
0196                 freeTop = m_freeNodes;
0197                 last->next = freeTop;
0198             } while (!m_freeNodes.testAndSetOrdered(freeTop, cleanChain));
0199         }
0200     }
0201 
0202     inline void freeList(Node *first) {
0203         Node *next;
0204         while (first) {
0205             next = first->next;
0206             delete first;
0207             first = next;
0208         }
0209     }
0210 
0211 
0212 private:
0213     Q_DISABLE_COPY(KisLocklessStack)
0214 
0215     QAtomicPointer<Node> m_top;
0216     QAtomicPointer<Node> m_freeNodes;
0217 
0218     QAtomicInt m_deleteBlockers;
0219     QAtomicInt m_numNodes;
0220 };
0221 
0222 #endif /* __KIS_LOCKLESS_STACK_H */
0223