BKE_reports: make the API thread-safe.

This commit makes using (most of) `BKE_report` API safe in
multi-threaded situation.

This is achieved by adding a `std::mutex` lock to the `ReportList`
struct (in a slightly convoluted way unfortunately, due to this being a
DNA struct). This lock is then used to make most operations on
`Reportlist` data thread-safe.

Note that while working on this, a few other minor issues aroze in
existing usages of Reportlist by the WM code, mainly the fact that
`wm_init_reports` and `wm_free_reports` were both useless:
  - init was called in a context where there is not yet any WM, so it
    was doing nothing.
  - free was called on a WM that would be later freed (as part of Main
    freeing), which would also call cleanup code for its `reports` data.
Both have been removed.

Further more, `wm_add_default` (which is the only place where a WM ID is
created) did not initialize properly it reports data, this has been
fixed.

This change is related to the wmJob thread-safety tasks and PRs (#112537,
!113548).

Pull Request: https://projects.blender.org/blender/blender/pulls/113561
This commit is contained in:
Bastien Montagne
2023-10-13 11:29:59 +02:00
committed by Bastien Montagne
parent db2328436e
commit 9859622a66
22 changed files with 140 additions and 58 deletions

View File

@@ -18,21 +18,47 @@
extern "C" {
#endif
/* Reporting Information and Errors
/** Reporting Information and Errors.
*
* These functions also accept NULL in case no error reporting
* is needed. */
* These functions are thread-safe, unless otherwise specified.
*
* These functions also accept nullptr in case no error reporting is needed. The message are only
* printed to the console then. */
/* Report structures are stored in DNA. */
/** Initialize a #ReportList struct.
*
* \note: Not thread-safe, should only be called from the 'owner' thread of the report list.
*/
void BKE_reports_init(ReportList *reports, int flag);
/**
* Only frees the list \a reports.
* Fully release any internal resources used by this #ReportList, as acquired by #BKE_reports_init.
*
* Also calls #BKE_reports_clear. The given `reports` should not be used anymore unless it is
* re-initialized first.
*
* \note: Not thread-safe, should only be called from the current owner of the report list, once
* no other concurrent access is possible.
*/
void BKE_reports_free(ReportList *reports);
/**
* Only frees the list of reports in given \a reports. Use #BKE_reports_free to fully cleanup all
* allocated resources.
*
* To make displayed reports disappear, either remove window-manager reports
* (#wmWindowManager.reports, or #CTX_wm_reports()), or use #WM_report_banners_cancel().
*/
void BKE_reports_clear(ReportList *reports);
/** Moves all reports from `reports_src` to `reports_dst`. */
void BKE_reports_move_to_reports(ReportList *reports_dst, ReportList *reports_src);
/** (Un)lock given `reports`, in case external code needs to access its data. */
void BKE_reports_lock(ReportList *reports);
void BKE_reports_unlock(ReportList *reports);
void BKE_report(ReportList *reports, eReportType type, const char *message);
void BKE_reportf(ReportList *reports, eReportType type, const char *format, ...)
ATTR_PRINTF_FORMAT(3, 4);

View File

@@ -10,11 +10,13 @@
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <mutex>
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_dynstr.h"
#include "BLI_listbase.h"
#include "BLI_string_utils.h"
#include "BLI_utildefines.h"
@@ -60,6 +62,20 @@ void BKE_reports_init(ReportList *reports, int flag)
reports->storelevel = RPT_INFO;
reports->printlevel = RPT_ERROR;
reports->flag = flag;
reports->lock = MEM_new<std::mutex>(__func__);
}
void BKE_reports_free(ReportList *reports)
{
if (!reports) {
return;
}
BKE_reports_clear(reports);
MEM_delete(reports->lock);
reports->lock = nullptr;
}
void BKE_reports_clear(ReportList *reports)
@@ -70,6 +86,8 @@ void BKE_reports_clear(ReportList *reports)
return;
}
std::scoped_lock lock(*reports->lock);
report = static_cast<Report *>(reports->list.first);
while (report) {
@@ -82,6 +100,28 @@ void BKE_reports_clear(ReportList *reports)
BLI_listbase_clear(&reports->list);
}
void BKE_reports_lock(ReportList *reports)
{
reports->lock->lock();
}
void BKE_reports_unlock(ReportList *reports)
{
reports->lock->unlock();
}
void BKE_reports_move_to_reports(ReportList *reports_dst, ReportList *reports_src)
{
BLI_assert(reports_dst);
if (!reports_src) {
return;
}
std::scoped_lock lock(*reports_src->lock, *reports_dst->lock);
BLI_movelisttolist(&reports_dst->list, &reports_src->list);
}
void BKE_report(ReportList *reports, eReportType type, const char *_message)
{
Report *report;
@@ -94,6 +134,8 @@ void BKE_report(ReportList *reports, eReportType type, const char *_message)
}
if (reports && (reports->flag & RPT_STORE) && (type >= reports->storelevel)) {
std::scoped_lock lock(*reports->lock);
char *message_alloc;
report = static_cast<Report *>(MEM_callocN(sizeof(Report), "Report"));
report->type = type;
@@ -124,6 +166,8 @@ void BKE_reportf(ReportList *reports, eReportType type, const char *_format, ...
}
if (reports && (reports->flag & RPT_STORE) && (type >= reports->storelevel)) {
std::scoped_lock lock(*reports->lock);
report = static_cast<Report *>(MEM_callocN(sizeof(Report), "Report"));
va_start(args, _format);
@@ -145,6 +189,9 @@ static void reports_prepend_impl(ReportList *reports, const char *prepend)
{
/* Caller must ensure. */
BLI_assert(reports && reports->list.first);
std::scoped_lock lock(*reports->lock);
const size_t prefix_len = strlen(prepend);
LISTBASE_FOREACH (Report *, report, &reports->list) {
char *message = BLI_string_joinN(prepend, report->message);
@@ -193,6 +240,8 @@ void BKE_report_print_level_set(ReportList *reports, eReportType level)
return;
}
std::scoped_lock lock(*reports->lock);
reports->printlevel = level;
}
@@ -211,6 +260,8 @@ void BKE_report_store_level_set(ReportList *reports, eReportType level)
return;
}
std::scoped_lock lock(*reports->lock);
reports->storelevel = level;
}
@@ -223,6 +274,8 @@ char *BKE_reports_string(ReportList *reports, eReportType level)
return nullptr;
}
std::scoped_lock lock(*reports->lock);
ds = BLI_dynstr_new();
LISTBASE_FOREACH (Report *, report, &reports->list) {
if (report->type >= level) {
@@ -274,6 +327,8 @@ void BKE_reports_print(ReportList *reports, eReportType level)
Report *BKE_reports_last_displayable(ReportList *reports)
{
std::scoped_lock lock(*reports->lock);
LISTBASE_FOREACH_BACKWARD (Report *, report, &reports->list) {
if (ELEM(report->type, RPT_ERROR, RPT_WARNING, RPT_INFO)) {
return report;
@@ -286,6 +341,8 @@ Report *BKE_reports_last_displayable(ReportList *reports)
bool BKE_reports_contain(ReportList *reports, eReportType level)
{
if (reports != nullptr) {
std::scoped_lock lock(*reports->lock);
LISTBASE_FOREACH (Report *, report, &reports->list) {
if (report->type >= level) {
return true;
@@ -301,6 +358,8 @@ bool BKE_report_write_file_fp(FILE *fp, ReportList *reports, const char *header)
fputs(header, fp);
}
std::scoped_lock lock(*reports->lock);
LISTBASE_FOREACH (Report *, report, &reports->list) {
fprintf((FILE *)fp, "%s # %s\n", report->message, report->typestr);
}

View File

@@ -120,7 +120,7 @@ bool BlendfileLoadingBaseTest::blendfile_load(const char *filepath)
char abspath[FILE_MAX];
BLI_path_join(abspath, sizeof(abspath), test_assets_dir.c_str(), filepath);
BlendFileReadReport bf_reports = {nullptr};
BlendFileReadReport bf_reports = {};
bfile = BLO_read_from_file(abspath, BLO_READ_SKIP_NONE, &bf_reports);
if (bfile == nullptr) {
ADD_FAILURE() << "Unable to load file '" << filepath << "' from test assets dir '"
@@ -143,10 +143,6 @@ void BlendfileLoadingBaseTest::blendfile_free()
return;
}
wmWindowManager *wm = static_cast<wmWindowManager *>(bfile->main->wm.first);
if (wm != nullptr) {
wm_close_and_free(nullptr, wm);
}
BLO_blendfiledata_free(bfile);
bfile = nullptr;
}

View File

@@ -566,6 +566,8 @@ void UI_popup_menu_reports(bContext *C, ReportList *reports)
return;
}
BKE_reports_lock(reports);
LISTBASE_FOREACH (Report *, report, &reports->list) {
int icon;
const char *msg, *msg_next;
@@ -601,6 +603,8 @@ void UI_popup_menu_reports(bContext *C, ReportList *reports)
} while ((msg = msg_next) && *msg);
}
BKE_reports_unlock(reports);
if (pup) {
UI_popup_menu_end(C, pup);
}

View File

@@ -1065,6 +1065,7 @@ static void write_result(TaskPool *__restrict pool, WriteTaskData *task_data)
}
}
if (reports.list.first != nullptr) {
/* TODO: Should rather use new #BKE_reports_move_to_reports ? */
BLI_spin_lock(&oglrender->reports_lock);
for (Report *report = static_cast<Report *>(reports.list.first); report != nullptr;
report = report->next)
@@ -1073,6 +1074,7 @@ static void write_result(TaskPool *__restrict pool, WriteTaskData *task_data)
}
BLI_spin_unlock(&oglrender->reports_lock);
}
BKE_reports_free(&reports);
if (!ok) {
oglrender->pool_ok = false;
}

View File

@@ -129,7 +129,7 @@ static void file_free(SpaceLink *sl)
MEM_SAFE_FREE(sfile->params);
MEM_SAFE_FREE(sfile->asset_params);
if (sfile->runtime != nullptr) {
BKE_reports_clear(&sfile->runtime->is_blendfile_readable_reports);
BKE_reports_free(&sfile->runtime->is_blendfile_readable_reports);
}
MEM_SAFE_FREE(sfile->runtime);

View File

@@ -2336,7 +2336,7 @@ bool ED_image_should_save_modified(const Main *bmain)
uint modified_images_count = ED_image_save_all_modified_info(bmain, &reports);
bool should_save = modified_images_count || !BLI_listbase_is_empty(&reports.list);
BKE_reports_clear(&reports);
BKE_reports_free(&reports);
return should_save;
}

View File

@@ -14,6 +14,13 @@
#include "DNA_ID.h"
#ifdef __cplusplus
# include <mutex>
using std_mutex_type = std::mutex;
#else
# define std_mutex_type void
#endif
/* Defined here: */
struct wmWindow;
@@ -98,6 +105,9 @@ typedef struct ReportList {
int flag;
char _pad[4];
struct wmTimer *reporttimer;
/** Mutex for thread-safety, runtime only. */
std_mutex_type *lock;
} ReportList;
/* Timer custom-data to control reports display. */
@@ -162,6 +172,7 @@ typedef struct wmWindowManager {
* \note keep in sync with `notifier_queue` adding/removing elements must also update this set.
*/
struct GSet *notifier_queue_set;
void *_pad1;
/** Information and error reports. */
struct ReportList reports;

View File

@@ -227,6 +227,8 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
ctx->object, (ModifierData *)dtmd, "Enable 'Auto Smooth' in Object Data Properties");
}
BKE_reports_free(&reports);
return result;
}

View File

@@ -33,7 +33,7 @@ short BPy_reports_to_error(ReportList *reports, PyObject *exception, const bool
report_str = BKE_reports_string(reports, RPT_ERROR);
if (clear == true) {
BKE_reports_clear(reports);
BKE_reports_free(reports);
}
if (report_str) {

View File

@@ -18,7 +18,10 @@ extern "C" {
struct ReportList;
/* error reporting */
/** Error reporting: convert BKE_report (#ReportList) reports into python errors.
*
* \param clear: When `true`, #BKE_reports_free is called on the given `reports`, which should
* then be considered as 'freed' data and not used anymore. */
short BPy_reports_to_error(struct ReportList *reports, PyObject *exception, bool clear);
/**
* A version of #BKE_report_write_file_fp that uses Python's stdout.

View File

@@ -281,11 +281,9 @@ static bool bpy_run_string_impl(bContext *C,
ReportList *wm_reports = CTX_wm_reports(C);
if (wm_reports) {
BLI_movelisttolist(&wm_reports->list, &reports.list);
}
else {
BKE_reports_clear(&reports);
BKE_reports_move_to_reports(wm_reports, &reports);
}
BKE_reports_free(&reports);
}
else {
Py_DECREF(retval);

View File

@@ -575,6 +575,8 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject * /*args*/)
BKE_blendfile_link_append_context_free(lapp_context);
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false);
BKE_reports_free(&self->reports);
Py_RETURN_NONE;
}

View File

@@ -187,17 +187,18 @@ static PyObject *bpy_lib_write(BPy_PropertyRNA *self, PyObject *args, PyObject *
if (retval) {
BKE_reports_print(&reports, RPT_ERROR_ALL);
BKE_reports_clear(&reports);
ret = Py_None;
Py_INCREF(ret);
}
else {
if (BPy_reports_to_error(&reports, PyExc_IOError, true) == 0) {
if (BPy_reports_to_error(&reports, PyExc_IOError, false) == 0) {
PyErr_SetString(PyExc_IOError, "Unknown error writing library data");
}
ret = nullptr;
}
BKE_reports_free(&reports);
finally:
/* clear all flags for ID's added to the store (may run on error too) */

View File

@@ -263,6 +263,7 @@ static PyObject *pyop_call(PyObject * /*self*/, PyObject *args)
BKE_reports_clear(reports);
if ((reports->flag & RPT_FREE) == 0) {
BKE_reports_free(reports);
MEM_freeN(reports);
}
else {

View File

@@ -8792,7 +8792,8 @@ static int bpy_class_call(bContext *C, PointerRNA *ptr, FunctionRNA *func, Param
BKE_reports_init(&reports_temp, reports->flag | RPT_PRINT_HANDLED_BY_OWNER);
reports_temp.storelevel = reports->storelevel;
BPy_errors_to_report(&reports_temp);
BLI_movelisttolist(&reports->list, &reports_temp.list);
BKE_reports_move_to_reports(reports, &reports_temp);
BKE_reports_free(&reports_temp);
}
/* Also print in the console for Python. */
@@ -9006,11 +9007,12 @@ static PyObject *pyrna_register_class(PyObject * /*self*/, PyObject *py_class)
if (!has_error) {
BPy_reports_write_stdout(&reports, error_prefix);
}
BKE_reports_clear(&reports);
if (has_error) {
BKE_reports_free(&reports);
return nullptr;
}
}
BKE_reports_free(&reports);
/* Python errors validating are not converted into reports so the check above will fail.
* the cause for returning nullptr will be printed as an error */

View File

@@ -102,7 +102,7 @@ void wm_gizmogroup_free(bContext *C, wmGizmoGroup *gzgroup)
#endif
if (gzgroup->reports && (gzgroup->reports->flag & RPT_FREE)) {
BKE_reports_clear(gzgroup->reports);
BKE_reports_free(gzgroup->reports);
MEM_freeN(gzgroup->reports);
}

View File

@@ -293,7 +293,7 @@ void WM_operator_free(wmOperator *op)
}
if (op->reports && (op->reports->flag & RPT_FREE)) {
BKE_reports_clear(op->reports);
BKE_reports_free(op->reports);
MEM_freeN(op->reports);
}
@@ -344,7 +344,7 @@ void WM_operator_type_set(wmOperator *op, wmOperatorType *ot)
static void wm_reports_free(wmWindowManager *wm)
{
BKE_reports_clear(&wm->reports);
BKE_reports_free(&wm->reports);
WM_event_timer_remove(wm, nullptr, wm->reports.reporttimer);
}
@@ -524,6 +524,8 @@ void wm_add_default(Main *bmain, bContext *C)
WorkSpace *workspace;
WorkSpaceLayout *layout = BKE_workspace_layout_find_global(bmain, screen, &workspace);
BKE_reports_init(&wm->reports, RPT_STORE);
CTX_wm_manager_set(C, wm);
win = wm_window_new(bmain, wm, nullptr, false);
win->scene = CTX_data_scene(C);

View File

@@ -935,7 +935,7 @@ static void wm_add_reports(ReportList *reports)
wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
/* Add reports to the global list, otherwise they are not seen. */
BLI_movelisttolist(&wm->reports.list, &reports->list);
BKE_reports_move_to_reports(&wm->reports, reports);
WM_report_banner_show(wm, nullptr);
}
@@ -950,7 +950,7 @@ void WM_report(eReportType type, const char *message)
wm_add_reports(&reports);
BKE_reports_clear(&reports);
BKE_reports_free(&reports);
}
void WM_reportf(eReportType type, const char *format, ...)
@@ -2831,12 +2831,7 @@ static eHandlerActionFlag wm_handler_fileselect_do(bContext *C,
BKE_report_print_level_set(handler->op->reports, RPT_WARNING);
UI_popup_menu_reports(C, handler->op->reports);
/* XXX: copied from #wm_operator_finished(). */
/* Add reports to the global list, otherwise they are not seen. */
BLI_movelisttolist(&CTX_wm_reports(C)->list, &handler->op->reports->list);
/* More hacks, since we meddle with reports, banner display doesn't happen automatic. */
WM_report_banner_show(CTX_wm_manager(C), CTX_wm_window(C));
wm_add_reports(handler->op->reports);
CTX_wm_window_set(C, win_prev);
CTX_wm_area_set(C, area_prev);

View File

@@ -4278,7 +4278,7 @@ static uiBlock *block_create__close_file_dialog(bContext *C, ARegion *region, vo
has_extra_checkboxes = true;
}
BKE_reports_clear(&reports);
BKE_reports_free(&reports);
uiItemS_ex(layout, has_extra_checkboxes ? 2.0f : 4.0f);

View File

@@ -137,19 +137,6 @@ CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_MSGBUS_SUB, "wm.msgbus.sub");
static void wm_init_scripts_extensions_once(bContext *C);
static void wm_init_reports(bContext *C)
{
ReportList *reports = CTX_wm_reports(C);
BLI_assert(!reports || BLI_listbase_is_empty(&reports->list));
BKE_reports_init(reports, RPT_STORE);
}
static void wm_free_reports(wmWindowManager *wm)
{
BKE_reports_clear(&wm->reports);
}
static bool wm_start_with_console = false;
void WM_init_state_start_with_console_set(bool value)
@@ -265,10 +252,6 @@ void WM_init(bContext *C, int argc, const char **argv)
BKE_icons_init(BIFICONID_LAST_STATIC);
BKE_preview_images_init();
/* Reports can't be initialized before the window-manager,
* but keep before file reading, since that may report errors */
wm_init_reports(C);
WM_msgbus_types_init();
/* Studio-lights needs to be init before we read the home-file,
@@ -619,11 +602,6 @@ void WM_exit_ex(bContext *C, const bool do_python_exit, const bool do_user_exit_
ED_preview_restart_queue_free();
ED_assetlist_storage_exit();
if (wm) {
/* Before BKE_blender_free! - since the ListBases get freed there. */
wm_free_reports(wm);
}
SEQ_clipboard_free(); /* `sequencer.cc` */
BKE_tracking_clipboard_free();
BKE_mask_clipboard_free();

View File

@@ -1815,7 +1815,7 @@ static int arg_handle_render_frame(int argc, const char **argv, void *data)
}
}
RE_SetReports(re, nullptr);
BKE_reports_clear(&reports);
BKE_reports_free(&reports);
MEM_freeN(frame_range_arr);
return 1;
}
@@ -1842,7 +1842,7 @@ static int arg_handle_render_animation(int /*argc*/, const char ** /*argv*/, voi
RE_RenderAnim(
re, bmain, scene, nullptr, nullptr, scene->r.sfra, scene->r.efra, scene->r.frame_step);
RE_SetReports(re, nullptr);
BKE_reports_clear(&reports);
BKE_reports_free(&reports);
}
else {
fprintf(stderr, "\nError: no blend loaded. cannot use '-a'.\n");
@@ -2180,7 +2180,7 @@ static bool handle_load_file(bContext *C, const char *filepath_arg, const bool l
BKE_reports_init(&reports, RPT_PRINT);
WM_file_autoexec_init(filepath);
const bool success = WM_file_read(C, filepath, &reports);
BKE_reports_clear(&reports);
BKE_reports_free(&reports);
if (success) {
if (G.background) {