Add more thread safety to background jobs

Fixes https://gitlab.com/kicad/code/kicad/-/issues/15395
This commit is contained in:
Marek Roszko 2023-08-11 22:37:43 -04:00
parent 7fe80abdff
commit 34a13cb0c5
3 changed files with 55 additions and 27 deletions

View File

@ -40,7 +40,7 @@
class BACKGROUND_JOB_PANEL : public wxPanel class BACKGROUND_JOB_PANEL : public wxPanel
{ {
public: public:
BACKGROUND_JOB_PANEL( wxWindow* aParent, BACKGROUND_JOB* aJob ) : BACKGROUND_JOB_PANEL( wxWindow* aParent, std::shared_ptr<BACKGROUND_JOB> aJob ) :
wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxSize( -1, 75 ), wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxSize( -1, 75 ),
wxBORDER_SIMPLE ), wxBORDER_SIMPLE ),
m_job( aJob ) m_job( aJob )
@ -86,7 +86,7 @@ private:
wxGauge* m_progress; wxGauge* m_progress;
wxStaticText* m_stName; wxStaticText* m_stName;
wxStaticText* m_stStatus; wxStaticText* m_stStatus;
BACKGROUND_JOB* m_job; std::shared_ptr<BACKGROUND_JOB> m_job;
}; };
@ -127,7 +127,7 @@ public:
} }
void Add( BACKGROUND_JOB* aJob ) void Add( std::shared_ptr<BACKGROUND_JOB> aJob )
{ {
BACKGROUND_JOB_PANEL* panel = new BACKGROUND_JOB_PANEL( m_scrolledWindow, aJob ); BACKGROUND_JOB_PANEL* panel = new BACKGROUND_JOB_PANEL( m_scrolledWindow, aJob );
m_contentSizer->Add( panel, 0, wxEXPAND | wxALL, 2 ); m_contentSizer->Add( panel, 0, wxEXPAND | wxALL, 2 );
@ -141,7 +141,7 @@ public:
} }
void Remove( BACKGROUND_JOB* aJob ) void Remove( std::shared_ptr<BACKGROUND_JOB> aJob )
{ {
auto it = m_jobPanels.find( aJob ); auto it = m_jobPanels.find( aJob );
if( it != m_jobPanels.end() ) if( it != m_jobPanels.end() )
@ -154,7 +154,7 @@ public:
} }
} }
void UpdateJob( BACKGROUND_JOB* aJob ) void UpdateJob( std::shared_ptr<BACKGROUND_JOB> aJob )
{ {
auto it = m_jobPanels.find( aJob ); auto it = m_jobPanels.find( aJob );
if( it != m_jobPanels.end() ) if( it != m_jobPanels.end() )
@ -167,12 +167,12 @@ public:
private: private:
wxScrolledWindow* m_scrolledWindow; wxScrolledWindow* m_scrolledWindow;
wxBoxSizer* m_contentSizer; wxBoxSizer* m_contentSizer;
std::unordered_map<BACKGROUND_JOB*, BACKGROUND_JOB_PANEL*> m_jobPanels; std::unordered_map<std::shared_ptr<BACKGROUND_JOB>, BACKGROUND_JOB_PANEL*> m_jobPanels;
}; };
BACKGROUND_JOB_REPORTER::BACKGROUND_JOB_REPORTER( BACKGROUND_JOBS_MONITOR* aMonitor, BACKGROUND_JOB_REPORTER::BACKGROUND_JOB_REPORTER( BACKGROUND_JOBS_MONITOR* aMonitor,
BACKGROUND_JOB* aJob ) : std::shared_ptr<BACKGROUND_JOB> aJob ) :
PROGRESS_REPORTER_BASE( 1 ), PROGRESS_REPORTER_BASE( 1 ),
m_monitor( aMonitor ), m_job( aJob ) m_monitor( aMonitor ), m_job( aJob )
{ {
@ -215,13 +215,14 @@ BACKGROUND_JOBS_MONITOR::BACKGROUND_JOBS_MONITOR() : m_jobListDialog( nullptr )
} }
BACKGROUND_JOB* BACKGROUND_JOBS_MONITOR::Create( const wxString& aName ) std::shared_ptr<BACKGROUND_JOB> BACKGROUND_JOBS_MONITOR::Create( const wxString& aName )
{ {
BACKGROUND_JOB* job = new BACKGROUND_JOB(); std::shared_ptr<BACKGROUND_JOB> job = std::make_shared<BACKGROUND_JOB>();
job->m_name = aName; job->m_name = aName;
job->m_reporter = std::make_shared<BACKGROUND_JOB_REPORTER>( this, job ); job->m_reporter = std::make_shared<BACKGROUND_JOB_REPORTER>( this, job );
std::lock_guard<std::shared_mutex> lock( m_mutex );
m_jobs.push_back( job ); m_jobs.push_back( job );
if( m_shownDialogs.size() > 0 ) if( m_shownDialogs.size() > 0 )
@ -229,7 +230,11 @@ BACKGROUND_JOB* BACKGROUND_JOBS_MONITOR::Create( const wxString& aName )
// update dialogs // update dialogs
for( BACKGROUND_JOB_LIST* list : m_shownDialogs ) for( BACKGROUND_JOB_LIST* list : m_shownDialogs )
{ {
list->Add( job ); list->CallAfter(
[=]()
{
list->Add( job );
} );
} }
} }
@ -237,7 +242,7 @@ BACKGROUND_JOB* BACKGROUND_JOBS_MONITOR::Create( const wxString& aName )
} }
void BACKGROUND_JOBS_MONITOR::Remove( BACKGROUND_JOB* aJob ) void BACKGROUND_JOBS_MONITOR::Remove( std::shared_ptr<BACKGROUND_JOB> aJob )
{ {
if( m_shownDialogs.size() > 0 ) if( m_shownDialogs.size() > 0 )
{ {
@ -245,12 +250,17 @@ void BACKGROUND_JOBS_MONITOR::Remove( BACKGROUND_JOB* aJob )
for( BACKGROUND_JOB_LIST* list : m_shownDialogs ) for( BACKGROUND_JOB_LIST* list : m_shownDialogs )
{ {
list->Remove( aJob ); list->CallAfter(
[=]()
{
list->Remove( aJob );
} );
} }
} }
std::lock_guard<std::shared_mutex> lock( m_mutex );
m_jobs.erase( std::remove_if( m_jobs.begin(), m_jobs.end(), m_jobs.erase( std::remove_if( m_jobs.begin(), m_jobs.end(),
[&]( BACKGROUND_JOB* job ) [&]( std::shared_ptr<BACKGROUND_JOB> job )
{ {
return job == aJob; return job == aJob;
} ) ); } ) );
@ -259,12 +269,14 @@ void BACKGROUND_JOBS_MONITOR::Remove( BACKGROUND_JOB* aJob )
{ {
for( KISTATUSBAR* statusBar : m_statusBars ) for( KISTATUSBAR* statusBar : m_statusBars )
{ {
statusBar->HideBackgroundProgressBar(); statusBar->CallAfter(
statusBar->SetBackgroundStatusText( wxT( "" ) ); [=]()
{
statusBar->HideBackgroundProgressBar();
statusBar->SetBackgroundStatusText( wxT( "" ) );
} );
} }
} }
delete aJob;
} }
@ -286,11 +298,15 @@ void BACKGROUND_JOBS_MONITOR::ShowList( wxWindow* aParent, wxPoint aPos )
{ {
BACKGROUND_JOB_LIST* list = new BACKGROUND_JOB_LIST( aParent, aPos ); BACKGROUND_JOB_LIST* list = new BACKGROUND_JOB_LIST( aParent, aPos );
for( BACKGROUND_JOB* job : m_jobs ) std::shared_lock<std::shared_mutex> lock( m_mutex, std::try_to_lock );
for( std::shared_ptr<BACKGROUND_JOB> job : m_jobs )
{ {
list->Add( job ); list->Add( job );
} }
lock.unlock();
m_shownDialogs.push_back( list ); m_shownDialogs.push_back( list );
list->Bind( wxEVT_CLOSE_WINDOW, &BACKGROUND_JOBS_MONITOR::onListWindowClosed, this ); list->Bind( wxEVT_CLOSE_WINDOW, &BACKGROUND_JOBS_MONITOR::onListWindowClosed, this );
@ -303,11 +319,12 @@ void BACKGROUND_JOBS_MONITOR::ShowList( wxWindow* aParent, wxPoint aPos )
} }
void BACKGROUND_JOBS_MONITOR::jobUpdated( BACKGROUND_JOB* aJob ) void BACKGROUND_JOBS_MONITOR::jobUpdated( std::shared_ptr<BACKGROUND_JOB> aJob )
{ {
std::shared_lock<std::shared_mutex> lock( m_mutex, std::try_to_lock );
// this method is called from the reporters from potentially other threads // this method is called from the reporters from potentially other threads
// we have to guard ui calls with CallAfter // we have to guard ui calls with CallAfter
if( m_jobs.size() > 0 ) if( m_jobs.size() > 0 )
{ {
//for now, we go and update the status bar if its the first job in the vector //for now, we go and update the status bar if its the first job in the vector
@ -328,6 +345,7 @@ void BACKGROUND_JOBS_MONITOR::jobUpdated( BACKGROUND_JOB* aJob )
} }
} }
for( BACKGROUND_JOB_LIST* list : m_shownDialogs ) for( BACKGROUND_JOB_LIST* list : m_shownDialogs )
{ {
list->CallAfter( list->CallAfter(

View File

@ -28,6 +28,7 @@
#include <widgets/progress_reporter_base.h> #include <widgets/progress_reporter_base.h>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <shared_mutex>
#include <vector> #include <vector>
class PROGRESS_REPORTER; class PROGRESS_REPORTER;
@ -43,7 +44,8 @@ class wxCloseEvent;
class BACKGROUND_JOB_REPORTER : public PROGRESS_REPORTER_BASE class BACKGROUND_JOB_REPORTER : public PROGRESS_REPORTER_BASE
{ {
public: public:
BACKGROUND_JOB_REPORTER( BACKGROUND_JOBS_MONITOR* aMonitor, BACKGROUND_JOB* aJob ); BACKGROUND_JOB_REPORTER( BACKGROUND_JOBS_MONITOR* aMonitor,
std::shared_ptr<BACKGROUND_JOB> aJob );
void SetTitle( const wxString& aTitle ) override void SetTitle( const wxString& aTitle ) override
{ {
@ -61,7 +63,7 @@ private:
bool updateUI() override; bool updateUI() override;
BACKGROUND_JOBS_MONITOR* m_monitor; BACKGROUND_JOBS_MONITOR* m_monitor;
BACKGROUND_JOB* m_job; std::shared_ptr<BACKGROUND_JOB> m_job;
wxString m_title; wxString m_title;
wxString m_report; wxString m_report;
}; };
@ -92,12 +94,12 @@ public:
* *
* @param aName is the displayed title for the event * @param aName is the displayed title for the event
*/ */
BACKGROUND_JOB* Create( const wxString& aName ); std::shared_ptr<BACKGROUND_JOB> Create( const wxString& aName );
/** /**
* Removes the given background job from any lists and frees it * Removes the given background job from any lists and frees it
*/ */
void Remove( BACKGROUND_JOB* job ); void Remove( std::shared_ptr<BACKGROUND_JOB> job );
/** /**
* Shows the background job list * Shows the background job list
@ -123,14 +125,22 @@ private:
/** /**
* Handles job status updates, intended to be called by BACKGROUND_JOB_REPORTER only * Handles job status updates, intended to be called by BACKGROUND_JOB_REPORTER only
*/ */
void jobUpdated( BACKGROUND_JOB* aJob ); void jobUpdated( std::shared_ptr<BACKGROUND_JOB> aJob );
BACKGROUND_JOB_LIST* m_jobListDialog; BACKGROUND_JOB_LIST* m_jobListDialog;
std::vector<BACKGROUND_JOB*> m_jobs; /**
* Holds a reference to all active background jobs
* Access to this vector should be protected by locks since threads may Create or Remove at will
* to register their activity
*/
std::vector<std::shared_ptr<BACKGROUND_JOB>> m_jobs;
std::vector<BACKGROUND_JOB_LIST*> m_shownDialogs; std::vector<BACKGROUND_JOB_LIST*> m_shownDialogs;
std::vector<KISTATUSBAR*> m_statusBars; std::vector<KISTATUSBAR*> m_statusBars;
/// Mutex to protect access to the m_jobs vector
mutable std::shared_mutex m_mutex;
}; };
#endif #endif

View File

@ -401,7 +401,7 @@ private:
std::function<void( int )> m_availableUpdateCallback; std::function<void( int )> m_availableUpdateCallback;
std::thread m_updateThread; std::thread m_updateThread;
BACKGROUND_JOB* m_updateBackgroundJob; std::shared_ptr<BACKGROUND_JOB> m_updateBackgroundJob;
}; };
#endif // PCM_H_ #endif // PCM_H_