Core: always free memory on exit, always report leaks

Instead of allowing leaks when parsing arguments, always cleanup before
calling exit(). This impacts -a (animation player), --help & --version
arguments, as well as scripts executed via --python which meant tests
that ran scripts could leak memory without raising an error as intended.

Avoid having suppress warnings & rationalize in code-comments when
leaking memory is/isn't acceptable, any leaks from the animation-player
are now reported as well.

This change exposed leaks: !140182, !140116.

Ref !140098
This commit is contained in:
Campbell Barton
2025-06-11 09:22:29 +00:00
parent 07121d44ae
commit f8eec542f4
5 changed files with 43 additions and 38 deletions

View File

@@ -269,12 +269,6 @@ extern void (*MEM_name_ptr_set)(void *vmemh, const char *str) ATTR_NONNULL();
*/
void MEM_init_memleak_detection(void);
/**
* Use this if we want to call #exit during argument parsing for example,
* without having to free all data.
*/
void MEM_use_memleak_detection(bool enabled);
/**
* When this has been called and memory leaks have been detected, the process will have an exit
* code that indicates failure. This can be used for when checking for memory leaks with automated

View File

@@ -24,15 +24,11 @@ char free_after_leak_detection_message[] =
namespace {
bool fail_on_memleak = false;
bool ignore_memleak = false;
class MemLeakPrinter {
public:
~MemLeakPrinter()
{
if (ignore_memleak) {
return;
}
leak_detector_has_run = true;
const uint leaked_blocks = MEM_get_memory_blocks_in_use();
if (leaked_blocks == 0) {
@@ -83,11 +79,6 @@ void MEM_init_memleak_detection()
static MemLeakPrinter printer;
}
void MEM_use_memleak_detection(bool enabled)
{
ignore_memleak = !enabled;
}
void MEM_enable_fail_on_memleak()
{
fail_on_memleak = true;

View File

@@ -47,6 +47,7 @@
#include "MOV_read.hh"
#include "MOV_util.hh"
#include "BKE_blender.hh"
#include "BKE_image.hh"
#include "BIF_glutil.hh"
@@ -2249,15 +2250,8 @@ int WM_main_playanim(int argc, const char **argv)
AUD_exitOnce();
#endif
/* NOTE(@ideasman42): Not useful unless all subsystems are properly shutdown. */
if (false) {
const int totblock = MEM_get_memory_blocks_in_use();
if (totblock != 0) {
/* Prints many `bAKey`, `bArgument` messages which are tricky to fix. */
printf("Error Totblock: %d\n", totblock);
MEM_printmemlist();
}
}
/* Cleanup sub-systems started before this function was called. */
BKE_blender_atexit();
return exit_code.value();
}

View File

@@ -144,6 +144,11 @@ static void main_callback_setup()
MEM_set_error_callback(callback_mem_error);
}
/** Data to free when Blender exits early on. */
struct CreatorAtExitData_EarlyExit {
bContext *C;
};
/** Free data on early exit (if Python calls `sys.exit()` while parsing args for eg). */
struct CreatorAtExitData {
#ifndef WITH_PYTHON_MODULE
@@ -155,9 +160,11 @@ struct CreatorAtExitData {
int argv_num;
#endif
#if defined(WITH_PYTHON_MODULE) && !defined(USE_WIN32_UNICODE_ARGS)
void *_empty; /* Prevent empty struct error with MSVC. */
#endif
/**
* When non-null, run additional exit logic.
* Cleared once early initialization is over.
*/
CreatorAtExitData_EarlyExit *early_exit = nullptr;
};
static void callback_main_atexit(void *user_data)
@@ -169,8 +176,6 @@ static void callback_main_atexit(void *user_data)
BLI_args_destroy(app_init_data->ba);
app_init_data->ba = nullptr;
}
#else
UNUSED_VARS(app_init_data); /* May be unused. */
#endif
#ifdef USE_WIN32_UNICODE_ARGS
@@ -181,9 +186,21 @@ static void callback_main_atexit(void *user_data)
free((void *)app_init_data->argv);
app_init_data->argv = nullptr;
}
#else
UNUSED_VARS(app_init_data); /* May be unused. */
#endif
if (CreatorAtExitData_EarlyExit *early_exit = app_init_data->early_exit) {
CTX_free(early_exit->C);
RE_texture_rng_exit();
BKE_brush_system_exit();
BKE_blender_globals_clear();
BKE_appdir_exit();
DNA_sdna_current_free();
CLG_exit();
}
}
static void callback_clg_fatal(void *fp)
@@ -288,6 +305,9 @@ int main(int argc,
CreatorAtExitData app_init_data = {nullptr};
BKE_blender_atexit_register(callback_main_atexit, &app_init_data);
CreatorAtExitData_EarlyExit app_init_data_early_exit = {nullptr};
app_init_data.early_exit = &app_init_data_early_exit;
/* Un-buffered `stdout` makes `stdout` and `stderr` better synchronized, and helps
* when stepping through code in a debugger (prints are immediately
* visible). However disabling buffering causes lock contention on windows
@@ -366,6 +386,8 @@ int main(int argc,
C = CTX_create();
app_init_data_early_exit.C = C;
#ifdef WITH_PYTHON_MODULE
# ifdef __APPLE__
environ = *_NSGetEnviron();
@@ -439,10 +461,6 @@ int main(int argc,
main_args_setup(C, ba, false);
/* Begin argument parsing, ignore leaks so arguments that call #exit
* (such as `--version` & `--help`) don't report leaks. */
MEM_use_memleak_detection(false);
/* Parse environment handling arguments. */
BLI_args_parse(ba, ARG_PASS_ENVIRONMENT, nullptr, nullptr);
@@ -476,6 +494,9 @@ int main(int argc,
main_signal_setup();
#endif
/* Continue with regular initialization, no need to use "early" exit. */
app_init_data.early_exit = nullptr;
/* Must be initialized after #BKE_appdir_init to account for color-management paths. */
IMB_init();
/* Keep after #ARG_PASS_SETTINGS since debug flags are checked. */
@@ -543,9 +564,6 @@ int main(int argc,
callback_main_atexit(&app_init_data);
BKE_blender_atexit_unregister(callback_main_atexit, &app_init_data);
/* End argument parsing, allow memory leaks to be printed. */
MEM_use_memleak_detection(true);
/* Paranoid, avoid accidental re-use. */
#ifndef WITH_PYTHON_MODULE
ba = nullptr;

View File

@@ -35,6 +35,7 @@
# endif
# include "BKE_appdir.hh"
# include "BKE_blender.hh"
# include "BKE_blender_cli_command.hh"
# include "BKE_blender_version.h"
# include "BKE_blendfile.hh"
@@ -627,6 +628,10 @@ static const char arg_handle_print_version_doc[] =
static int arg_handle_print_version(int /*argc*/, const char ** /*argv*/, void * /*data*/)
{
print_version_full();
/* Handles cleanup before exit. */
BKE_blender_atexit();
exit(EXIT_SUCCESS);
BLI_assert_unreachable();
return 0;
@@ -922,6 +927,9 @@ static int arg_handle_print_help(int /*argc*/, const char ** /*argv*/, void *dat
print_help(ba, false);
/* Handles cleanup before exit. */
BKE_blender_atexit();
exit(EXIT_SUCCESS);
BLI_assert_unreachable();