Coverage Report

Created: 2026-05-16 09:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/primitive2d/BufferedDecompositionFlusher.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <sal/config.h>
21
#include <comphelper/solarmutex.hxx>
22
#include <drawinglayer/primitive2d/BufferedDecompositionFlusher.hxx>
23
#include <drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx>
24
#include <drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx>
25
26
namespace drawinglayer::primitive2d
27
{
28
/**
29
    This is a "garbage collection" approach to flushing.
30
31
    We store entries in a set. Every 2 seconds, we scan the set for entries that have not
32
    been used for 10 seconds or more, and if so, we flush the buffer primitives in those entries.
33
34
    This mechanism is __deliberately__ not perfect.
35
    Sometimes things will be flushed a little too soon, sometimes things will wait a little too long,
36
    since we only have a granularity of 2 seconds.
37
    But what is gains from not being perfect, is scalability.
38
39
    It is very simple, scales to lots and lots of primitives without needing lots of timers, and performs
40
    very little work in the common case.
41
42
    Shutdown notes
43
    --------------------
44
    The process of handling shutdown is more complicated here than it should be, because we are interacting with
45
    various vcl-level things (by virtue of calling into drawinglayer primitives that use vcl facilities), but we
46
    do not have access to vcl-level API here (like SolarMutexReleaser and vcl::Timer).
47
*/
48
49
static BufferedDecompositionFlusher* getInstance()
50
0
{
51
0
    static std::unique_ptr<BufferedDecompositionFlusher> gaTimer(new BufferedDecompositionFlusher);
52
0
    return gaTimer.get();
53
0
}
54
55
// static
56
void BufferedDecompositionFlusher::shutdown()
57
0
{
58
0
    BufferedDecompositionFlusher* pFlusher = getInstance();
59
0
    pFlusher->onTeardown();
60
    // We have to wait for the thread to exit, otherwise we might end up with the background thread
61
    // trying to process stuff while it has things ripped out underneath it.
62
0
    pFlusher->join();
63
0
}
64
65
// static
66
void BufferedDecompositionFlusher::update(const BufferedDecompositionPrimitive2D* p)
67
0
{
68
0
    getInstance()->updateImpl(p);
69
0
}
70
71
// static
72
void BufferedDecompositionFlusher::update(const BufferedDecompositionGroupPrimitive2D* p)
73
0
{
74
0
    getInstance()->updateImpl(p);
75
0
}
76
77
// static
78
void BufferedDecompositionFlusher::remove(const BufferedDecompositionPrimitive2D* p)
79
0
{
80
0
    getInstance()->removeImpl(p);
81
0
}
82
83
// static
84
void BufferedDecompositionFlusher::remove(const BufferedDecompositionGroupPrimitive2D* p)
85
0
{
86
0
    getInstance()->removeImpl(p);
87
0
}
88
89
0
BufferedDecompositionFlusher::BufferedDecompositionFlusher() { create(); }
90
91
void BufferedDecompositionFlusher::updateImpl(const BufferedDecompositionPrimitive2D* p)
92
0
{
93
0
    std::unique_lock l(maMutex);
94
0
    if (!mbShutdown)
95
0
    {
96
0
        unotools::WeakReference<BufferedDecompositionPrimitive2D> xRef(
97
0
            const_cast<BufferedDecompositionPrimitive2D*>(p));
98
0
        maRegistered1.insert({ p, std::move(xRef) });
99
0
    }
100
0
}
101
102
void BufferedDecompositionFlusher::updateImpl(const BufferedDecompositionGroupPrimitive2D* p)
103
0
{
104
0
    std::unique_lock l(maMutex);
105
0
    if (!mbShutdown)
106
0
    {
107
0
        unotools::WeakReference<BufferedDecompositionGroupPrimitive2D> xRef(
108
0
            const_cast<BufferedDecompositionGroupPrimitive2D*>(p));
109
0
        maRegistered2.insert({ p, std::move(xRef) });
110
0
    }
111
0
}
112
113
void BufferedDecompositionFlusher::removeImpl(const BufferedDecompositionPrimitive2D* p)
114
0
{
115
0
    std::unique_lock l(maMutex);
116
0
    if (!mbShutdown)
117
0
        maRegistered1.erase(p);
118
0
}
119
120
void BufferedDecompositionFlusher::removeImpl(const BufferedDecompositionGroupPrimitive2D* p)
121
0
{
122
0
    std::unique_lock l(maMutex);
123
0
    if (!mbShutdown)
124
0
        maRegistered2.erase(p);
125
0
}
126
127
void SAL_CALL BufferedDecompositionFlusher::run()
128
0
{
129
0
    setName("BufferedDecompositionFlusher");
130
0
    for (;;)
131
0
    {
132
0
        auto aNow = std::chrono::steady_clock::now();
133
0
        std::vector<rtl::Reference<BufferedDecompositionPrimitive2D>> aRemoved1;
134
0
        std::vector<rtl::Reference<BufferedDecompositionGroupPrimitive2D>> aRemoved2;
135
0
        std::vector<rtl::Reference<BasePrimitive2D>> aDelayRelease;
136
0
        {
137
0
            std::unique_lock l1(maMutex);
138
            // exit if we have been shutdown
139
0
            if (mbShutdown)
140
0
                break;
141
0
            for (auto it = maRegistered1.begin(); it != maRegistered1.end();)
142
0
            {
143
0
                rtl::Reference<BufferedDecompositionPrimitive2D> xPrimitive = it->second.get();
144
0
                if (!xPrimitive)
145
0
                    it = maRegistered1.erase(it);
146
0
                else if (aNow - xPrimitive->maLastAccess.load() > std::chrono::seconds(10))
147
0
                {
148
0
                    aRemoved1.push_back(std::move(xPrimitive));
149
0
                    it = maRegistered1.erase(it);
150
0
                }
151
0
                else
152
0
                {
153
0
                    aDelayRelease.push_back(std::move(xPrimitive));
154
0
                    ++it;
155
0
                }
156
0
            }
157
0
            for (auto it = maRegistered2.begin(); it != maRegistered2.end();)
158
0
            {
159
0
                rtl::Reference<BufferedDecompositionGroupPrimitive2D> xPrimitive = it->second.get();
160
0
                if (!xPrimitive)
161
0
                    it = maRegistered2.erase(it);
162
0
                else if (aNow - xPrimitive->maLastAccess.load() > std::chrono::seconds(10))
163
0
                {
164
0
                    aRemoved2.push_back(std::move(xPrimitive));
165
0
                    it = maRegistered2.erase(it);
166
0
                }
167
0
                else
168
0
                {
169
0
                    aDelayRelease.push_back(std::move(xPrimitive));
170
0
                    ++it;
171
0
                }
172
0
            }
173
0
        }
174
        // There is a very very small window where, if :
175
        // This-thread: we create a strong reference from a weak reference inside the loop
176
        // Another-thread: releases the second last strong reference to the object
177
        // This-thread: we clear the reference, which triggers object destruction, which tries to call back
178
        //  into BufferedDecompositionFlusher and then deadlocks because the mutex is already acquired.
179
0
        aDelayRelease.clear();
180
181
0
        {
182
            // some parts of skia do not take kindly to being accessed from multiple threads
183
0
            osl::Guard<comphelper::SolarMutex> aGuard(comphelper::SolarMutex::get());
184
185
0
            for (const auto& xPrim : aRemoved1)
186
0
            {
187
0
                xPrim->setBuffered2DDecomposition(nullptr);
188
0
            }
189
0
            for (const auto& xPrim : aRemoved2)
190
0
            {
191
0
                xPrim->setBuffered2DDecomposition(Primitive2DContainer{});
192
0
            }
193
194
            // Clear these while under the SolarMutex, just in case we are the sole surviving reference,
195
            // and we might trigger destruction of related vcl resources.
196
0
            aRemoved1.clear();
197
0
            aRemoved2.clear();
198
0
        }
199
200
0
        {
201
0
            std::unique_lock l(maMutex);
202
0
            maDelayOrTerminate.wait_for(l, std::chrono::seconds(2), [this] { return mbShutdown; });
203
0
        }
204
0
    }
205
0
}
206
207
/// Only called by FlusherDeinit
208
void BufferedDecompositionFlusher::onTeardown()
209
0
{
210
0
    {
211
0
        std::unique_lock l2(maMutex);
212
0
        mbShutdown = true;
213
0
        maRegistered1.clear();
214
0
        maRegistered2.clear();
215
0
    }
216
0
    maDelayOrTerminate.notify_all();
217
0
}
218
219
} // end of namespace drawinglayer::primitive2d
220
221
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */