Skip to content

Commit 6eb8e19

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 6eb8e19

4 files changed

Lines changed: 69 additions & 9 deletions

File tree

src/include/OpenImageIO/imageio.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3968,6 +3968,16 @@ 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. Note that this can
3979+
/// also be enabled with the `OIIO_USTRING_CLEANUP` environment variable.
3980+
///
39713981
/// EXAMPLES:
39723982
/// ```
39733983
/// // 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: 40 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,25 @@ 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 entries
72+
// and everything we malloced.
73+
ustring_write_lock_t lock(mutex);
74+
for (size_t i = 0; i <= mask; ++i) {
75+
if (entries[i])
76+
entries[i]->~TableRep();
77+
}
78+
free(entries);
79+
for (auto p : pool_allocs)
80+
free(p);
81+
} else {
82+
/* just let memory leak */
83+
}
5084
}
5185

5286
size_t get_memory_usage()
@@ -187,13 +221,16 @@ template<unsigned BASE_CAPACITY, unsigned POOL_SIZE> struct TableRepMap {
187221

188222
if (len >= POOL_SIZE) {
189223
memory_usage += len;
190-
return (char*)malloc(len); // no need to try and use the pool
224+
char* r = (char*)malloc(len); // no need to try and use the pool
225+
pool_allocs.push_back(r);
226+
return r;
191227
}
192228
if (pool_offset + len > POOL_SIZE) {
193229
// NOTE: old pool will leak - this is ok because ustrings cannot be freed
194230
memory_usage += POOL_SIZE;
195231
pool = (char*)malloc(POOL_SIZE);
196232
pool_offset = 0;
233+
pool_allocs.push_back(pool);
197234
}
198235
char* result = pool + pool_offset;
199236
pool_offset += len;
@@ -207,6 +244,7 @@ template<unsigned BASE_CAPACITY, unsigned POOL_SIZE> struct TableRepMap {
207244
char* pool;
208245
size_t pool_offset = 0;
209246
size_t memory_usage;
247+
std::vector<char*> pool_allocs;
210248
#ifdef USTRING_TRACK_NUM_LOOKUPS
211249
size_t num_lookups = 0;
212250
#endif

0 commit comments

Comments
 (0)