MGR: Async RPCs: use wxCondition with timeout to block main thread while waiting for Demand RPC to finish; set m_pTaskBarIcon and m_pMacSystemMenu to NULL when deleted.

svn path=/trunk/boinc/; revision=16517
This commit is contained in:
Charlie Fenton 2008-11-18 13:28:26 +00:00
parent 03c91a5616
commit 1a5c93ff19
8 changed files with 142 additions and 63 deletions

View File

@ -30,6 +30,8 @@
// Delay in milliseconds before showing AsyncRPCDlg
#define RPC_WAIT_DLG_DELAY 1500
// How often to check for events when minimized and waiting for Demand RPC
#define DELAY_WHEN_MINIMIZED 500
// Delay in milliseconds to allow thread to exit before killing it
#define RPC_KILL_DELAY 100
@ -99,10 +101,17 @@ int AsyncRPC::RPC_Wait(RPC_SELECTOR which_rpc, void *arg1, void *arg2,
}
RPCThread::RPCThread(CMainDocument *pDoc, wxMutex* pRPC_Thread_Mutex, wxCondition* pRPC_Thread_Condition) : wxThread() {
RPCThread::RPCThread(CMainDocument *pDoc,
wxMutex* pRPC_Thread_Mutex,
wxCondition* pRPC_Thread_Condition,
wxMutex* pRPC_Request_Mutex,
wxCondition* pRPC_Request_Condition)
: wxThread() {
m_pDoc = pDoc;
m_pRPC_Thread_Mutex = pRPC_Thread_Mutex;
m_pRPC_Thread_Condition = pRPC_Thread_Condition;
m_pRPC_Request_Mutex = pRPC_Request_Mutex;
m_pRPC_Request_Condition = pRPC_Request_Condition;
}
@ -116,6 +125,7 @@ void *RPCThread::Entry() {
int retval = 0;
CRPCFinishedEvent RPC_done_event( wxEVT_RPC_FINISHED );
ASYNC_RPC_REQUEST *current_request;
wxMutexError mutexErr = wxMUTEX_NO_ERROR;
wxCondError condErr = wxCOND_NO_ERROR;
m_pRPC_Thread_Mutex->Lock();
@ -123,8 +133,8 @@ void *RPCThread::Entry() {
while(true) {
// Wait for main thread to wake us
// This does the following:
// (1) Unlocks the Mutex an puts the thread to sleep as an atomic operation.
// (2) On Signal from main thread, wakes and then automatically locks m_pRPC_Thread_Mutex again.
// (1) Unlocks the Mutex and puts the RPC thread to sleep as an atomic operation.
// (2) On Signal from main thread: locks Mutex again and wakes the RPC thread.
if (!m_pDoc->m_bShutDownRPCThread) {
condErr = m_pRPC_Thread_Condition->Wait();
wxASSERT(condErr == wxCOND_NO_ERROR);
@ -154,9 +164,19 @@ void *RPCThread::Entry() {
retval = ProcessRPCRequest();
current_request->retval = retval;
current_request->isActive = false;
mutexErr = m_pRPC_Request_Mutex->Lock();
wxASSERT(mutexErr == wxMUTEX_NO_ERROR);
current_request->isActive = false;
wxPostEvent( wxTheApp, RPC_done_event );
// Signal() is ignored / discarded unless the main thread is
// currently blocked by m_pRPC_Request_Condition->Wait[Timeout]()
m_pRPC_Request_Condition->Signal();
mutexErr = m_pRPC_Request_Mutex->Unlock();
wxASSERT(mutexErr == wxMUTEX_NO_ERROR);
}
return NULL;
@ -401,8 +421,11 @@ int CMainDocument::RequestRPC(ASYNC_RPC_REQUEST& request, bool hasPriority) {
std::vector<ASYNC_RPC_REQUEST>::iterator iter;
int retval = 0;
wxMutexError mutexErr = wxMUTEX_NO_ERROR;
bool keepLooping = true;
long delayTimeRemaining, timeToSleep;
bool shown = false;
if (!m_RPCThread) return -1;
if ( (request.rpcType < RPC_TYPE_WAIT_FOR_COMPLETION) ||
(request.rpcType >= NUM_RPC_TYPES) ) {
wxASSERT(false);
@ -445,10 +468,10 @@ int CMainDocument::RequestRPC(ASYNC_RPC_REQUEST& request, bool hasPriority) {
mutexErr = m_pRPC_Thread_Mutex->Lock(); // Blocks until thread unlocks the mutex
wxASSERT(mutexErr == wxMUTEX_NO_ERROR);
m_pRPC_Thread_Condition->Signal(); // Unblock the thread
mutexErr = m_pRPC_Thread_Mutex->Unlock(); // Release the mutex so thread can lock it
wxASSERT(mutexErr == wxMUTEX_NO_ERROR);
m_pRPC_Thread_Condition->Signal(); // Unblock the thread
}
// If this is a user-initiated event wait for completion but show
@ -469,48 +492,62 @@ int CMainDocument::RequestRPC(ASYNC_RPC_REQUEST& request, bool hasPriority) {
// Allow RPC_WAIT_DLG_DELAY seconds for Demand RPC to complete before
// displaying "Please Wait" dialog, but keep checking for completion.
do {
#ifdef __WXMSW__
SwitchToThread();
#else
// TODO: is there a way for main UNIX thread to yield wih no minimum delay?
timespec ts = {0, 1}; /// 1 nanosecond
nanosleep(&ts, NULL); /// 1 nanosecond or less
#endif
delayTimeRemaining = RPC_WAIT_DLG_DELAY;
while (true) {
if (delayTimeRemaining >= 0) { // Prevent overflow if minimized for a very long time
delayTimeRemaining = RPC_WAIT_DLG_DELAY - Dlgdelay.Time();
}
if (pFrame) {
shown = pFrame->IsShown();
} else {
shown = false;
}
if (shown) {
if (delayTimeRemaining <= 0) break; // Display the Please Wait dialog
timeToSleep = delayTimeRemaining;
} else {
// Don't show dialog while Manager is minimized, but do
// process events so user can maximize the manager.
//
// NOTE: CBOINCGUIApp::FilterEvent() blocks those events
// which might cause posting of more RPC requests while
// we are in this loop, to prevent undesirable recursion.
// It does allow wxEVT_RPC_FINISHED.
//
timeToSleep = DELAY_WHEN_MINIMIZED; // Allow user to maximize Manager
wxSafeYield(NULL, true);
}
// OnRPCComplete() clears m_bWaitingForRPC if RPC completed
if (! m_bWaitingForRPC) {
return retval;
}
keepLooping = (Dlgdelay.Time() < RPC_WAIT_DLG_DELAY);
if (keepLooping) {
// Simulate handling of CRPCFinishedEvent but don't allow any other
// events (so no user activity) to prevent undesirable recursion.
// Allow RPC thread to run while we wait for it.
if (!current_rpc_request.isActive) {
HandleCompletedRPC();
}
} else {
// RPC_WAIT_DLG_DELAY has expired; check if Manager is minimized.
if (pFrame) {
if (!pFrame->IsShown()) {
// Don't show dialog while Manager is minimized, but do
// process events so user can maximize the manager.
//
// NOTE: CBOINCGUIApp::FilterEvent() blocks those events
// which might cause posting of more RPC requests while
// we are in this loop, to prevent undesirable recursion.
// It does allow wxEVT_RPC_FINISHED so we don't need to
// explicity call HandleCompletedRPC() here.
//
keepLooping = true;
if (wxGetApp().Pending()) {
wxGetApp().Dispatch();
}
}
}
mutexErr = m_pRPC_Request_Mutex->Lock();
wxASSERT(mutexErr == wxMUTEX_NO_ERROR);
// Simulate handling of CRPCFinishedEvent but don't allow any other
// events (so no user activity) to prevent undesirable recursion.
// Allow RPC thread to run while we wait for it.
if (!current_rpc_request.isActive) {
mutexErr = m_pRPC_Request_Mutex->Unlock();
wxASSERT(mutexErr == wxMUTEX_NO_ERROR);
HandleCompletedRPC();
continue;
}
} while (keepLooping);
// Wait for RPC thread to wake us
// This does the following:
// (1) Unlocks the Mutex and puts the main thread to sleep as an atomic operation.
// (2) On Signal from RPC thread: locks Mutex again and wakes the main thread.
m_pRPC_Request_Condition->WaitTimeout(timeToSleep);
mutexErr = m_pRPC_Request_Mutex->Unlock();
wxASSERT(mutexErr == wxMUTEX_NO_ERROR);
}
// Demand RPC has taken longer than RPC_WAIT_DLG_DELAY seconds and
// Manager is not minimized, so display the "Please Wait" dialog
@ -568,10 +605,9 @@ void CMainDocument::KillRPCThread() {
}
rpcClient.close();
RPC_requests.clear();
current_rpc_request.clear();
m_bNeedRefresh = false;
m_bNeedTaskBarRefresh = false;
m_bShutDownRPCThread = true;
// On some platforms, Delete() takes effect only when thread calls TestDestroy()
@ -583,6 +619,10 @@ void CMainDocument::KillRPCThread() {
wxASSERT(mutexErr == wxMUTEX_NO_ERROR);
m_pRPC_Thread_Condition->Signal(); // Unblock the thread
m_RPCThread->Delete();
RPC_requests.clear();
current_rpc_request.clear();
wxStopWatch ThreadDeleteTimer = wxStopWatch();
// RPC thread sets m_RPCThread to NULL when it exits
@ -606,6 +646,8 @@ void CMainDocument::HandleCompletedRPC() {
wxMutexError mutexErr = wxMUTEX_NO_ERROR;
int i, n, requestIndex = -1;
bool stillWaitingForPendingRequests = false;
if (!m_RPCThread) return;
if (current_rpc_request.isActive) return;

View File

@ -287,7 +287,12 @@ private:
class RPCThread : public wxThread
{
public:
RPCThread(CMainDocument *pDoc, wxMutex* pRPC_Thread_Mutex, wxCondition* pRPC_Thread_Condition);
RPCThread(CMainDocument *pDoc,
wxMutex* pRPC_Thread_Mutex,
wxCondition* pRPC_Thread_Condition,
wxMutex* pRPC_Request_Mutex,
wxCondition* RPC_Request_Condition
);
virtual void *Entry();
virtual void OnExit();
@ -296,6 +301,8 @@ private:
CMainDocument* m_pDoc;
wxMutex* m_pRPC_Thread_Mutex;
wxCondition* m_pRPC_Thread_Condition;
wxMutex* m_pRPC_Request_Mutex;
wxCondition* m_pRPC_Request_Condition;
};

View File

@ -349,17 +349,12 @@ void CBOINCBaseFrame::OnExit(wxCommandEvent& WXUNUSED(event)) {
// Under wxWidgets 2.8.0, the task bar icons must be deleted for app to exit its main loop
#ifdef __WXMAC__
CMacSystemMenu* pMSM = wxGetApp().GetMacSystemMenu();
if (pMSM)
delete pMSM;
wxGetApp().DeleteMacSystemMenu();
#endif
// TaskBarIcon isn't used in Linux
#if defined(__WXMSW__) || defined(__WXMAC__)
CTaskBarIcon* pTBI = wxGetApp().GetTaskBarIcon();
if (pTBI && !pTBI->m_bTaskbarInitiatedShutdown) {
delete pTBI;
}
wxGetApp().DeleteTaskBarIcon();
#endif
Close(true);
}

View File

@ -971,6 +971,24 @@ bool CBOINCGUIApp::IsModalDialogDisplayed() {
}
void CBOINCGUIApp::DeleteTaskBarIcon() {
if (m_pTaskBarIcon) {
delete m_pTaskBarIcon;
}
m_pTaskBarIcon = NULL;
}
#ifdef __WXMAC__
void CBOINCGUIApp::DeleteMacSystemMenu() {
if (m_pMacSystemMenu) {
delete m_pMacSystemMenu;
}
m_pMacSystemMenu = NULL;
}
#endif
// Prevent recursive entry of CMainDocument::RequestRPC()
int CBOINCGUIApp::FilterEvent(wxEvent &event) {
if (!m_pDocument) return -1;

View File

@ -127,9 +127,11 @@ public:
wxString GetDataDirectory() { return m_strBOINCMGRDataDirectory; }
#if defined(__WXMSW__) || defined(__WXMAC__)
CTaskBarIcon* GetTaskBarIcon() { return m_pTaskBarIcon; }
void DeleteTaskBarIcon();
#endif
#ifdef __WXMAC__
CMacSystemMenu* GetMacSystemMenu() { return m_pMacSystemMenu; }
void DeleteMacSystemMenu();
int ShouldShutdownCoreClient()
{ return true; }
#else

View File

@ -443,8 +443,20 @@ int CMainDocument::OnInit() {
wxASSERT(m_pRPC_Thread_Mutex);
m_pRPC_Thread_Condition = new wxCondition(*m_pRPC_Thread_Mutex);
m_RPCThread = new RPCThread(this, m_pRPC_Thread_Mutex, m_pRPC_Thread_Condition);
wxASSERT(m_pRPC_Thread_Condition);
m_pRPC_Request_Mutex = new wxMutex();
wxASSERT(m_pRPC_Request_Mutex);
m_pRPC_Request_Condition = new wxCondition(*m_pRPC_Request_Mutex);
wxASSERT(m_pRPC_Request_Condition);
m_RPCThread = new RPCThread(this,
m_pRPC_Thread_Mutex,
m_pRPC_Thread_Condition,
m_pRPC_Request_Mutex,
m_pRPC_Request_Condition
);
wxASSERT(m_RPCThread);
iRetVal = m_RPCThread->Create();
@ -459,9 +471,6 @@ int CMainDocument::OnInit() {
int CMainDocument::OnExit() {
int iRetVal = 0;
RPC_requests.clear();
current_rpc_request.clear();
if (m_pClientManager) {
m_pClientManager->ShutdownBOINCCore();
@ -480,6 +489,12 @@ int CMainDocument::OnExit() {
delete m_pRPC_Thread_Condition;
m_pRPC_Thread_Condition = NULL;
delete m_pRPC_Request_Mutex;
m_pRPC_Request_Mutex = NULL;
delete m_pRPC_Request_Condition;
m_pRPC_Request_Condition = NULL;
rpcClient.close();
if (m_pNetworkConnection) {

View File

@ -182,7 +182,6 @@ public:
wxDialog* GetRPCWaitDialog() { return m_RPCWaitDlg; }
// void TestAsyncRPC(); // For testing Async RPCs
RPCThread* m_RPCThread;
wxCriticalSection m_critsect;
bool m_bShutDownRPCThread;
private:
@ -197,6 +196,8 @@ private:
bool m_bNeedTaskBarRefresh;
wxMutex* m_pRPC_Thread_Mutex;
wxCondition* m_pRPC_Thread_Condition;
wxMutex* m_pRPC_Request_Mutex;
wxCondition* m_pRPC_Request_Condition;
//
// Project Tab

View File

@ -299,6 +299,7 @@ pascal OSStatus SysMenuEventHandler( EventHandlerCallRef inHandlerCallRef,
return eventNotHandledErr;
pMSM = wxGetApp().GetMacSystemMenu();
if (!pMSM) break;
// wxMac "helpfully" converts wxID_ABOUT to kHICommandAbout, wxID_EXIT to kHICommandQuit,
// wxID_PREFERENCES to kHICommandPreferences
@ -352,10 +353,8 @@ pascal OSStatus SysMenuEventHandler( EventHandlerCallRef inHandlerCallRef,
// Note that if the main window is open, CBOINCBaseFrame::OnExit() will be
// called and SysMenuEventHandler() (i.e., this code) will not be called.
if (commandID == wxID_EXIT) {
delete pMSM;
CTaskBarIcon* pTBI = wxGetApp().GetTaskBarIcon();
if (pTBI)
delete pTBI;
wxGetApp().DeleteMacSystemMenu();
wxGetApp().DeleteTaskBarIcon();
}
return noErr ;
}