int CMainDocument::RequestRPC(ASYNC_RPC_REQUEST& request, bool hasPriority) { std::vector<ASYNC_RPC_REQUEST>::iterator iter; int retval = 0; int response = wxID_OK; wxMutexError mutexErr = wxMUTEX_NO_ERROR; 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); return -1; } // If we are quitting, cancel any pending RPCs if (request.which_rpc == RPC_QUIT) { if (current_rpc_request.isActive) { RPC_requests.erase(RPC_requests.begin()+1, RPC_requests.end()); } else { RPC_requests.clear(); } } // Check if a duplicate request is already on the queue for (iter=RPC_requests.begin(); iter!=RPC_requests.end(); iter++) { if (iter->isSameAs(request)) { return 0; } } if ((request.rpcType == RPC_TYPE_WAIT_FOR_COMPLETION) && (request.resultPtr == NULL)) { request.resultPtr = &retval; } if (hasPriority) { // We may want to set hasPriority for some user-initiated events. // Since the user is waiting, insert this at head of request queue. // As of 8/14/08, hasPriority is never set true, so hasn't been tested. iter = RPC_requests.insert(RPC_requests.begin(), request); } else { RPC_requests.push_back(request); } // Start this RPC if no other RPC is already in progress. if (RPC_requests.size() == 1) { // Wait for thread to unlock mutex with m_pRPC_Thread_Condition->Wait() mutexErr = m_pRPC_Thread_Mutex->Lock(); // Blocks until thread unlocks the mutex wxASSERT(mutexErr == wxMUTEX_NO_ERROR); // Make sure activation is an atomic operation request.isActive = false; current_rpc_request = request; current_rpc_request.isActive = true; m_pRPC_Thread_Condition->Signal(); // Unblock the thread // m_pRPC_Thread_Condition->Wait() will Lock() the mutex upon receiving Signal(), // causing it to block again if we still have our lock on the mutex. mutexErr = m_pRPC_Thread_Mutex->Unlock(); wxASSERT(mutexErr == wxMUTEX_NO_ERROR); } // If this is a user-initiated event wait for completion but show // a dialog allowing the user to cancel. if (request.rpcType == RPC_TYPE_WAIT_FOR_COMPLETION) { // TODO: proper handling if a second user request is received while first is pending ?? if (m_bWaitingForRPC) { wxLogMessage(wxT("Second user RPC request while another was pending")); wxASSERT(false); return -1; } // Don't show dialog if RPC completes before RPC_WAIT_DLG_DELAY // or while BOINC is minimized CBOINCBaseFrame* pFrame = wxGetApp().GetFrame(); wxStopWatch Dlgdelay = wxStopWatch(); m_RPCWaitDlg = new AsyncRPCDlg(); m_bWaitingForRPC = true; // Allow RPC_WAIT_DLG_DELAY seconds for Demand RPC to complete before // displaying "Please Wait" dialog, but keep checking for completion. 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() discards those events // which might cause posting of more RPC requests while // we are in this loop, to prevent undesirable recursion. // Since the manager is minimized, we don't have to worry about // discarding crucial drawing or command events. // The filter does allow the the Open Manager menu item from // the system tray icon and wxEVT_RPC_FINISHED event. // timeToSleep = DELAY_WHEN_MINIMIZED; // Allow user to maximize Manager wxSafeYield(NULL, true); } // OnRPCComplete() clears m_bWaitingForRPC if RPC completed if (! m_bWaitingForRPC) { return retval; } 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. // Since we don't need to filter and discard events, they remain on // the queue until it is safe to process them. // 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; } // 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 // with a Cancel button. If the RPC does complete while the dialog // is up, HandleCompletedRPC() will call EndModal with wxID_OK. // // NOTE: the Modal dialog permits processing of all events, but // CBOINCGUIApp::FilterEvent() blocks those events which might cause // posting of more RPC requests while in this dialog, to prevent // undesirable recursion. // if (m_RPCWaitDlg) { response = m_RPCWaitDlg->ShowModal(); // Remember time the dialog was closed for use by RunPeriodicRPCs() m_dtLasAsyncRPCDlgTime = wxDateTime::Now(); if (response != wxID_OK) { // TODO: If user presses Cancel in Please Wait dialog but request // has not yet been started, should we just remove it from queue? // If we make that change, should we also add a separate menu item // to reset the RPC connection (or does one already exist)? retval = -1; // If the RPC continues to get data after we return to // our caller, it may try to write into a buffer or struct // which the caller has already deleted. To prevent this, // we close the socket (disconnect) and kill the RPC thread. // This is ugly but necessary. We must then reconnect and // start a new RPC thread. if (current_rpc_request.isActive) { current_rpc_request.isActive = false; rpcClient.close(); RPC_requests.clear(); current_rpc_request.clear(); m_bNeedRefresh = false; m_bNeedTaskBarRefresh = false; // We will be reconnected to the same client (if possible) by // CBOINCDialUpManager::OnPoll() and CNetworkConnection::Poll(). m_pNetworkConnection->SetStateDisconnected(); } if (response == wxID_EXIT) { pFrame = wxGetApp().GetFrame(); wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED, wxID_EXIT); s_bSkipExitConfirmation = true; pFrame->AddPendingEvent(evt); } } if (m_RPCWaitDlg) { m_RPCWaitDlg->Destroy(); } m_RPCWaitDlg = NULL; m_bWaitingForRPC = false; } } return retval; }