mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-10 21:44:22 +00:00
This was mainly found in SpriteSizeJob crash reports. In these reports deleted image buffers were still used to paint the Editor canvas because the doc was write-locked in the main thread (same thread where the canvas is painted). This produced a re-entrant lock in the Editor::onPaint() as we can still read-lock from the same thread where we write-locked the doc. With this change we write-lock the doc from the SpriteJob background thread (not the main thread) only if it's necessary (i.e. when the doc is not already locked in the main thread, e.g. when running a script). This makes that the main thread (Editor::onPaint) cannot read the doc until we finish the whole SpriteJob transaction/Tx.
151 lines
2.8 KiB
C++
151 lines
2.8 KiB
C++
// Aseprite
|
|
// Copyright (C) 2021-2024 Igara Studio S.A.
|
|
// Copyright (C) 2001-2018 David Capello
|
|
//
|
|
// This program is distributed under the terms of
|
|
// the End-User License Agreement for Aseprite.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "app/job.h"
|
|
|
|
#include "app/app.h"
|
|
#include "app/console.h"
|
|
#include "app/context.h"
|
|
#include "app/i18n/strings.h"
|
|
#include "fmt/format.h"
|
|
#include "ui/alert.h"
|
|
#include "ui/widget.h"
|
|
#include "ui/window.h"
|
|
|
|
#include <atomic>
|
|
|
|
static const int kMonitoringPeriod = 100;
|
|
static std::atomic<int> g_runningJobs(0);
|
|
|
|
namespace app {
|
|
|
|
// static
|
|
int Job::runningJobs()
|
|
{
|
|
return g_runningJobs;
|
|
}
|
|
|
|
Job::Job(const std::string& jobName)
|
|
{
|
|
m_last_progress = 0.0;
|
|
m_done_flag = false;
|
|
m_canceled_flag = false;
|
|
|
|
if (App::instance()->isGui()) {
|
|
m_alert_window = ui::Alert::create(
|
|
fmt::format(Strings::alerts_job_working(), jobName));
|
|
m_alert_window->addProgress();
|
|
|
|
m_timer.reset(new ui::Timer(kMonitoringPeriod, m_alert_window.get()));
|
|
m_timer->Tick.connect(&Job::onMonitoringTick, this);
|
|
m_timer->start();
|
|
}
|
|
}
|
|
|
|
Job::~Job()
|
|
{
|
|
if (App::instance()->isGui()) {
|
|
ASSERT(!m_timer->isRunning());
|
|
|
|
if (m_alert_window)
|
|
m_alert_window->closeWindow(NULL);
|
|
}
|
|
}
|
|
|
|
void Job::startJob()
|
|
{
|
|
m_thread = std::thread(&Job::thread_proc, this);
|
|
++g_runningJobs;
|
|
|
|
if (m_alert_window) {
|
|
m_alert_window->openWindowInForeground();
|
|
|
|
// The job was canceled by the user?
|
|
{
|
|
std::unique_lock<std::mutex> hold(m_mutex);
|
|
if (!m_done_flag)
|
|
m_canceled_flag = true;
|
|
}
|
|
|
|
// In case of error, take the "cancel" path (i.e. it's like the
|
|
// user canceled the operation).
|
|
if (m_error) {
|
|
m_canceled_flag = true;
|
|
try {
|
|
std::rethrow_exception(m_error);
|
|
}
|
|
catch (const std::exception& ex) {
|
|
Console::showException(ex);
|
|
}
|
|
catch (...) {
|
|
Console console;
|
|
console.printf("Unknown error performing the task");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Job::waitJob()
|
|
{
|
|
if (m_timer && m_timer->isRunning())
|
|
m_timer->stop();
|
|
|
|
if (m_thread.joinable()) {
|
|
m_thread.join();
|
|
|
|
--g_runningJobs;
|
|
}
|
|
}
|
|
|
|
void Job::jobProgress(double f)
|
|
{
|
|
m_last_progress = f;
|
|
}
|
|
|
|
bool Job::isCanceled()
|
|
{
|
|
return m_canceled_flag;
|
|
}
|
|
|
|
void Job::onMonitoringTick()
|
|
{
|
|
std::unique_lock<std::mutex> hold(m_mutex);
|
|
|
|
// update progress
|
|
m_alert_window->setProgress(m_last_progress);
|
|
|
|
// is job done? we can close the monitor
|
|
if (m_done_flag || m_canceled_flag) {
|
|
m_timer->stop();
|
|
m_alert_window->closeWindow(NULL);
|
|
}
|
|
}
|
|
|
|
void Job::done()
|
|
{
|
|
std::unique_lock<std::mutex> hold(m_mutex);
|
|
m_done_flag = true;
|
|
}
|
|
|
|
// Called to start the worker thread.
|
|
void Job::thread_proc(Job* self)
|
|
{
|
|
try {
|
|
self->onJob();
|
|
}
|
|
catch (...) {
|
|
self->m_error = std::current_exception();
|
|
}
|
|
self->done();
|
|
}
|
|
|
|
} // namespace app
|