Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/graphic/Manager.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 <graphic/Manager.hxx>
21
#include <impgraph.hxx>
22
#include <sal/log.hxx>
23
24
#include <officecfg/Office/Common.hxx>
25
#include <unotools/configmgr.hxx>
26
27
using namespace css;
28
29
namespace
30
{
31
void setupConfigurationValuesIfPossible(sal_Int64& rMemoryLimit,
32
                                        std::chrono::seconds& rAllowedIdleTime, bool& bSwapEnabled)
33
22
{
34
22
    if (comphelper::IsFuzzing())
35
22
        return;
36
37
0
    try
38
0
    {
39
0
        using officecfg::Office::Common::Cache;
40
41
0
        rMemoryLimit = Cache::GraphicManager::GraphicMemoryLimit::get();
42
0
        rAllowedIdleTime
43
0
            = std::chrono::seconds(Cache::GraphicManager::GraphicAllowedIdleTime::get());
44
0
        bSwapEnabled = Cache::GraphicManager::GraphicSwappingEnabled::get();
45
0
    }
46
0
    catch (...)
47
0
    {
48
0
    }
49
0
}
50
}
51
52
namespace vcl::graphic
53
{
54
MemoryManager::MemoryManager()
55
22
    : maSwapOutTimer("MemoryManager::MemoryManager maSwapOutTimer")
56
22
{
57
22
    setupConfigurationValuesIfPossible(mnMemoryLimit, mnAllowedIdleTime, mbSwapEnabled);
58
59
22
    if (mbSwapEnabled)
60
22
    {
61
22
        maSwapOutTimer.SetPriority(TaskPriority::DEFAULT_IDLE);
62
22
        maSwapOutTimer.SetInvokeHandler(LINK(this, MemoryManager, ReduceMemoryTimerHandler));
63
22
        maSwapOutTimer.SetTimeout(mnTimeout);
64
22
        maSwapOutTimer.Stop();
65
22
    }
66
22
}
67
68
MemoryManager& MemoryManager::get()
69
526k
{
70
526k
    static MemoryManager gStaticManager;
71
526k
    return gStaticManager;
72
526k
}
73
74
IMPL_LINK_NOARG(MemoryManager, ReduceMemoryTimerHandler, Timer*, void)
75
0
{
76
0
    std::unique_lock aGuard(maMutex);
77
0
    maSwapOutTimer.Stop();
78
0
    reduceMemory(aGuard);
79
    // will be started again on size change
80
0
}
81
82
void MemoryManager::registerObject(MemoryManaged* pMemoryManaged)
83
166k
{
84
166k
    std::unique_lock aGuard(maMutex);
85
86
    // Insert and update the used size (bytes)
87
166k
    assert(aGuard.owns_lock() && aGuard.mutex() == &maMutex);
88
    // coverity[missing_lock: FALSE] - as above assert
89
    // Related: tdf#167007 Only add object bytes if the object is
90
    // actually inserted into the cache
91
166k
    if (maObjectList.insert(pMemoryManaged).second)
92
166k
        mnTotalSize += pMemoryManaged->getCurrentSizeInBytes();
93
166k
    checkStartReduceTimer();
94
166k
}
95
96
void MemoryManager::unregisterObject(MemoryManaged* pMemoryManaged)
97
166k
{
98
166k
    std::unique_lock aGuard(maMutex);
99
    // Related: tdf#167007 Only remove object size if the object is
100
    // actually removed from the cache
101
166k
    if (maObjectList.erase(pMemoryManaged))
102
166k
        mnTotalSize -= pMemoryManaged->getCurrentSizeInBytes();
103
166k
    checkStartReduceTimer();
104
166k
}
105
106
void MemoryManager::changeExisting(MemoryManaged* pMemoryManaged, sal_Int64 nNewSize)
107
194k
{
108
194k
    std::scoped_lock aGuard(maMutex);
109
    // Related: tdf#167007 Only change total cache bytes if the object
110
    // actually exists in the cache
111
194k
    if (maObjectList.find(pMemoryManaged) != maObjectList.end())
112
194k
    {
113
194k
        sal_Int64 nOldSize = pMemoryManaged->getCurrentSizeInBytes();
114
194k
        mnTotalSize -= nOldSize;
115
194k
        mnTotalSize += nNewSize;
116
194k
    }
117
194k
    pMemoryManaged->setCurrentSizeInBytes(nNewSize);
118
194k
    checkStartReduceTimer();
119
194k
}
120
121
void MemoryManager::swappedIn(MemoryManaged* pMemoryManaged, sal_Int64 nNewSize)
122
14.4k
{
123
14.4k
    changeExisting(pMemoryManaged, nNewSize);
124
14.4k
}
125
126
void MemoryManager::swappedOut(MemoryManaged* pMemoryManaged, sal_Int64 nNewSize)
127
0
{
128
0
    changeExisting(pMemoryManaged, nNewSize);
129
0
}
130
131
0
OUString MemoryManager::getCacheName() const { return "MemoryManager"; }
132
133
bool MemoryManager::dropCaches()
134
0
{
135
0
    std::unique_lock aGuard(maMutex);
136
0
    reduceMemory(aGuard, true);
137
0
    return true;
138
0
}
139
140
void MemoryManager::dumpState(rtl::OStringBuffer& rState)
141
0
{
142
0
    std::unique_lock aGuard(maMutex);
143
144
0
    rState.append("\nMemory Manager items:\t");
145
0
    rState.append(static_cast<sal_Int32>(maObjectList.size()));
146
0
    rState.append("\tsize:\t");
147
0
    rState.append(static_cast<sal_Int64>(mnTotalSize / 1024));
148
0
    rState.append("\tkb");
149
150
0
    for (MemoryManaged* pMemoryManaged : maObjectList)
151
0
    {
152
0
        pMemoryManaged->dumpState(rState);
153
0
    }
154
0
}
155
156
void MemoryManager::checkStartReduceTimer()
157
526k
{
158
    // maMutex is locked in callers
159
160
526k
    if (!mbSwapEnabled || mnTotalSize < mnMemoryLimit)
161
525k
        return;
162
163
    // start the timer
164
1.84k
    if (!maSwapOutTimer.IsActive())
165
6
        maSwapOutTimer.Start();
166
1.84k
}
167
168
void MemoryManager::reduceMemory(std::unique_lock<std::mutex>& rGuard, bool bDropAll)
169
0
{
170
    // maMutex is locked in callers
171
172
0
    if (!mbSwapEnabled)
173
0
        return;
174
175
0
    if (mnTotalSize < mnMemoryLimit && !bDropAll)
176
0
        return;
177
178
    // avoid recursive reduceGraphicMemory on reexport of tdf118346-1.odg to odg
179
0
    if (mbReducingGraphicMemory)
180
0
        return;
181
182
0
    mbReducingGraphicMemory = true;
183
184
0
    loopAndReduceMemory(rGuard, bDropAll);
185
186
0
    mbReducingGraphicMemory = false;
187
0
}
188
189
void MemoryManager::loopAndReduceMemory(std::unique_lock<std::mutex>& rGuard, bool bDropAll)
190
0
{
191
    // make a copy of m_pImpGraphicList because if we swap out a svg, the svg
192
    // filter may create more temp Graphics which are auto-added to
193
    // m_pImpGraphicList invalidating a loop over m_pImpGraphicList, e.g.
194
    // reexport of tdf118346-1.odg
195
196
0
    o3tl::sorted_vector<MemoryManaged*> aObjectListCopy = maObjectList;
197
198
0
    for (MemoryManaged* pMemoryManaged : aObjectListCopy)
199
0
    {
200
0
        if (!pMemoryManaged->canReduceMemory())
201
0
            continue;
202
203
0
        sal_Int64 nCurrentSizeInBytes = pMemoryManaged->getCurrentSizeInBytes();
204
0
        if (nCurrentSizeInBytes > mnSmallFrySize || bDropAll) // ignore small-fry
205
0
        {
206
0
            auto aCurrent = std::chrono::high_resolution_clock::now();
207
0
            auto aDeltaTime = aCurrent - pMemoryManaged->getLastUsed();
208
0
            auto aSeconds = std::chrono::duration_cast<std::chrono::seconds>(aDeltaTime);
209
210
0
            if (aSeconds > mnAllowedIdleTime)
211
0
            {
212
                // unlock because svgio can call back into us
213
0
                rGuard.unlock();
214
0
                pMemoryManaged->reduceMemory();
215
0
                rGuard.lock();
216
0
            }
217
0
        }
218
0
    }
219
0
}
220
221
} // end vcl::graphic
222
223
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */