Skip to content

Commit e2e216c

Browse files
committed
feat: allow freeing of ustring table for debugging cases
This patch optionally frees the contents of the ustring table as the process is exiting. It's triggered one of two ways: - `OIIO::attribute("ustring:cleanup", 1);` - Environment variable `OIIO_USTRING_CLEANUP=1` If neither is used, the teardown won't happen, thus avoiding any pointless work when it should be exiting the process. The purpose of this is that when using certain debugging tools, the ustring table (which only grows and never frees) looks like a memory leak, even though it's by design. Someimes, it's useful to make it not appear ot leak, and you are happy for a large table to spend time pointlessly freeing all the data. Fixes 3913 Signed-off-by: Larry Gritz <lg@larrygritz.com>
1 parent df6c3ea commit e2e216c

4 files changed

Lines changed: 69 additions & 9 deletions

File tree

src/include/OpenImageIO/imageio.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3968,6 +3968,15 @@ OIIO_API std::string geterror(bool clear = true);
39683968
/// enable globally in an environment where security is a higher priority
39693969
/// than being tolerant of partially broken image files.
39703970
///
3971+
/// - `ustring:cleanup` (int: 0)
3972+
///
3973+
/// If nonzero, upon exit, do a thorough (and possibly expensive) teardown
3974+
/// of ustring internal resources to ensure that there are no apparent
3975+
/// memory leaks. This is only desirable in certain debugging situations.
3976+
/// Ordinarily, it is better to finish as quickly as possible, so the
3977+
/// default of 0 skips a time consuming and pointless teardown of the
3978+
/// ustring internal allocations when the app exits.
3979+
///
39713980
/// EXAMPLES:
39723981
/// ```
39733982
/// // Setting single simple values simply:

src/include/imageio_pvt.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ extern atomic_ll IB_local_mem_current;
5353
extern atomic_ll IB_local_mem_peak;
5454
extern std::atomic<float> IB_total_open_time;
5555
extern std::atomic<float> IB_total_image_read_time;
56-
extern OIIO_UTIL_API int oiio_use_tbb; // This lives in libOpenImageIO_Util
56+
57+
// These live in libOpenImageIO_Util
58+
extern OIIO_UTIL_API int oiio_use_tbb;
59+
extern OIIO_UTIL_API int oiio_ustring_cleanup;
60+
5761
OIIO_API const std::vector<std::string>&
5862
font_dirs();
5963
OIIO_API const std::vector<std::string>&

src/libOpenImageIO/imageio.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,10 @@ attribute(string_view name, TypeDesc type, const void* val)
373373

374374
// Things below here need to buarded by the attrib_mutex
375375
std::lock_guard lock(attrib_mutex);
376+
if (name == "debug" && type == TypeInt) {
377+
oiio_print_debug = *(const int*)val;
378+
return true;
379+
}
376380
if (name == "read_chunk" && type == TypeInt) {
377381
oiio_read_chunk = *(const int*)val;
378382
return true;
@@ -447,10 +451,6 @@ attribute(string_view name, TypeDesc type, const void* val)
447451
oiio_use_tbb = *(const int*)val;
448452
return true;
449453
}
450-
if (name == "debug" && type == TypeInt) {
451-
oiio_print_debug = *(const int*)val;
452-
return true;
453-
}
454454
if (name == "log_times" && type == TypeInt) {
455455
oiio_log_times = *(const int*)val;
456456
return true;
@@ -471,6 +471,10 @@ attribute(string_view name, TypeDesc type, const void* val)
471471
oiio_try_all_readers = *(const int*)val;
472472
return true;
473473
}
474+
if (name == "ustring:cleanup" && type == TypeInt) {
475+
oiio_ustring_cleanup = *(const int*)val;
476+
return true;
477+
}
474478

475479
return false;
476480
}
@@ -511,6 +515,10 @@ getattribute(string_view name, TypeDesc type, void* val)
511515

512516
// Things below here need to buarded by the attrib_mutex
513517
std::lock_guard lock(attrib_mutex);
518+
if (name == "debug" && type == TypeInt) {
519+
*(int*)val = oiio_print_debug;
520+
return true;
521+
}
514522
if (name == "read_chunk" && type == TypeInt) {
515523
*(int*)val = oiio_read_chunk;
516524
return true;
@@ -649,8 +657,8 @@ getattribute(string_view name, TypeDesc type, void* val)
649657
*(int*)val = oiio_use_tbb;
650658
return true;
651659
}
652-
if (name == "debug" && type == TypeInt) {
653-
*(int*)val = oiio_print_debug;
660+
if (name == "ustring:cleanup" && type == TypeInt) {
661+
*(int*)val = oiio_ustring_cleanup;
654662
return true;
655663
}
656664
if (name == "log_times" && type == TypeInt) {

src/libutil/ustring.cpp

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,29 @@
88
#include <OpenImageIO/dassert.h>
99
#include <OpenImageIO/export.h>
1010
#include <OpenImageIO/strutil.h>
11+
#include <OpenImageIO/sysutil.h>
1112
#include <OpenImageIO/thread.h>
1213
#include <OpenImageIO/unordered_map_concurrent.h>
1314
#include <OpenImageIO/ustring.h>
1415

16+
17+
18+
OIIO_NAMESPACE_BEGIN
19+
namespace pvt {
20+
21+
// If nonzero, the ustring table will be freed at process exit. This is off by
22+
// default because cleanup is unnecessary (the OS reclaims the memory) and can
23+
// add measurable time at exit for large tables. Enable it when using valgrind
24+
// or other leak detectors to suppress false positives. Settable via
25+
// OIIO::attribute("ustring:cleanup",1) or the OIIO_USTRING_CLEANUP
26+
// environment variable.
27+
OIIO_UTIL_API int oiio_ustring_cleanup = Strutil::stoi(
28+
Sysutil::getenv("OIIO_USTRING_CLEANUP"));
29+
30+
} // namespace pvt
31+
OIIO_NAMESPACE_END
32+
33+
1534
OIIO_NAMESPACE_3_1_BEGIN
1635

1736
// Use rw spin locks
@@ -43,10 +62,26 @@ template<unsigned BASE_CAPACITY, unsigned POOL_SIZE> struct TableRepMap {
4362
, memory_usage(sizeof(*this) + POOL_SIZE
4463
+ sizeof(ustring::TableRep*) * BASE_CAPACITY)
4564
{
65+
pool_allocs.push_back(pool);
4666
}
4767

4868
~TableRepMap()
49-
{ /* just let memory leak */
69+
{
70+
if (OIIO::pvt::oiio_ustring_cleanup) {
71+
// If requested, take the time to properly destroy all the
72+
// entries, and also the unique_ptr arrays all_pools and
73+
// large_allocs will naturally free their contents after this
74+
// destructor body ends.
75+
for (size_t i = 0; i <= mask; ++i) {
76+
if (entries[i])
77+
entries[i]->~TableRep();
78+
}
79+
free(entries);
80+
for (auto p : pool_allocs)
81+
free(p);
82+
} else {
83+
/* just let memory leak */
84+
}
5085
}
5186

5287
size_t get_memory_usage()
@@ -187,13 +222,16 @@ template<unsigned BASE_CAPACITY, unsigned POOL_SIZE> struct TableRepMap {
187222

188223
if (len >= POOL_SIZE) {
189224
memory_usage += len;
190-
return (char*)malloc(len); // no need to try and use the pool
225+
char* r = (char*)malloc(len); // no need to try and use the pool
226+
pool_allocs.push_back(r);
227+
return r;
191228
}
192229
if (pool_offset + len > POOL_SIZE) {
193230
// NOTE: old pool will leak - this is ok because ustrings cannot be freed
194231
memory_usage += POOL_SIZE;
195232
pool = (char*)malloc(POOL_SIZE);
196233
pool_offset = 0;
234+
pool_allocs.push_back(pool);
197235
}
198236
char* result = pool + pool_offset;
199237
pool_offset += len;
@@ -207,6 +245,7 @@ template<unsigned BASE_CAPACITY, unsigned POOL_SIZE> struct TableRepMap {
207245
char* pool;
208246
size_t pool_offset = 0;
209247
size_t memory_usage;
248+
std::vector<char*> pool_allocs;
210249
#ifdef USTRING_TRACK_NUM_LOOKUPS
211250
size_t num_lookups = 0;
212251
#endif

0 commit comments

Comments
 (0)