Overview
Examples
Screenshots
Comparisons
Applications
Download
Documentation
Tutorials
Bazaar
Status & Roadmap
FAQ
Authors & License
Forums
Funding Ultimate++
Search on this site
Search in forums












SourceForge.net Logo
Home » U++ Library support » U++ MT-multithreading and servers » Problem breaking loop (with close button) in main thread
Problem breaking loop (with close button) in main thread [message #59034] Tue, 18 October 2022 17:19 Go to next message
awksed is currently offline  awksed
Messages: 61
Registered: April 2012
Member
Windows 7. U++ 16270. Multi-thread app.

I have a GuiAcquireMutex() function called from a main thread Paint() that calls

WaitForSingleObject(hMutex, dwShortTimeout) // dwShortTimeout = 1 second (full timeout is 60 seconds)

if the mutex is not acquired I call:

Ctrl::GuiSleep(1000) to allow other threads to run and if 60 seconds have not elapsed, jump
back to WaitForSingleObject().

I wish to allow the close button to call Close() (that sets boolean g_bQuit) and allow the user
close the app (breaking the mutex acquire loop).

Clicking the close button results in the windows message "... is not responding".

Adding Ctrl::ProcessEvents() causes a "WM_PAINT invoked ... while in Paint routine" error.

Is there some way in U++ to allow the close button to call Close() while the main thread is looping
(like Windows PumpWaitingMessages())?


Code:

DWORD GuiAcquireMutex(HANDLE hMutex, DWORD dwTimeout)
{
 if(hMutex == INVALID_HANDLE_VALUE)
   return ERROR_INVALID_HANDLE;
 
 long long llNow;
 long long llStart        = GetMilliTime();
 long long llEnd          = llStart + (long long) dwTimeout;
 DWORD     dwShortTimeout = 1000;        // 1 second
 DWORD     dwLastError;
 DWORD     dwWaitResult;

Again:

 dwLastError  = 0; // Success
 dwWaitResult = WaitForSingleObject(hMutex, dwShortTimeout);

 switch(dwWaitResult)
  {
   case WAIT_FAILED:

        llNow = GetMilliTime();
        
        if(llNow < llEnd)
         {
          if(g_bQuit)
           {
            dwLastError = ERROR_COUNTER_TIMEOUT;

            SetLastError(dwLastError);
            return dwLastError;
           }
          
          Ctrl::GuiSleep(1000); // Never sleeps for 1000 ms (always returns immediately)
          goto Again;
         }

        dwLastError = GetLastError();
        break;

   case WAIT_TIMEOUT:

        llNow = GetMilliTime();
        
        if(llNow < llEnd)
         {
          if(g_bQuit)
           {
            dwLastError = ERROR_COUNTER_TIMEOUT;

            SetLastError(dwLastError);
            return dwLastError;
           }
           
          Ctrl::GuiSleep(1000); // Never sleeps for 1000 ms (always returns immediately)
          goto Again;
         }

        dwLastError = ERROR_COUNTER_TIMEOUT;

        SetLastError(dwLastError);
  }

 return dwLastError;
}



Thanks.

Re: Problem breaking loop (with close button) in main thread [message #59035 is a reply to message #59034] Tue, 18 October 2022 19:15 Go to previous messageGo to next message
Lance is currently offline  Lance
Messages: 526
Registered: March 2007
Contributor
Hi Awksed:

I think what you can do is make your wait/check function(check_again()) re-entrant and instead of Sleep in the main thread, send check_again to a PostCallback and let the GUI thread(main thread) to arrange it be recalled after (roughly)specified time duration and do the check again.

Re: Problem breaking loop (with close button) in main thread [message #59036 is a reply to message #59034] Tue, 18 October 2022 20:09 Go to previous messageGo to next message
Lance is currently offline  Lance
Messages: 526
Registered: March 2007
Contributor
On a second thought, your program needs the hMutex be acquired to continue its work. Then ProcessEvent() should be the way to go. Problem is, why is that ProcessEvent() failed you?

Can you try something like the following
DWORD GuiAcquireMutex(HANDLE hMutex, DWORD dwTimeout)
{
	if(hMutex == INVALID_HANDLE_VALUE)
		return ERROR_INVALID_HANDLE;
 
	DWORD     dwLastError = -1;

	while ( !g_bQuit && dwLastError != 0 )
	{
		dwLastError  = 0; // Success

		switch( WaitForSingleObject(hMutex, dwShortTimeout ) )
		{
		case WAIT_FAILED:
			Do_Set_LastError_Accordingly();
			break;
			
		case WAIT_TIMEOUT:
			dwLastError = ERROR_COUNTER_TIMEOUT;
			break;
		}
		// consider measure how long it takes ProcessEvents to finish,
		// if very quick, consider add a Sleep(500) or something like that
		// to avoid keeping CPU busy for nothing.
		topwin.ProcessEvents();
		if(TooSoon() )
		{
			Sleep (500);
		}
	}
	SetLastError (dwLastError) ;

	return dwLastError;
}


topwin is your TopWindow(or its derivative) object.

I am feeling your original code didnot prepare for the case when the WaitForSingleObject call actually succeeds. I might be wrong though. Above pseudo code will need to be tuned to reflect your true intention.

[Updated on: Tue, 18 October 2022 20:34]

Report message to a moderator

Re: Problem breaking loop (with close button) in main thread [message #59039 is a reply to message #59034] Wed, 19 October 2022 01:11 Go to previous messageGo to next message
awksed is currently offline  awksed
Messages: 61
Registered: April 2012
Member
Hi Lance,

Thanks for your reply.

Quote:

I am feeling your original code didnot prepare for the case when the WaitForSingleObject call actually succeeds


The function has worked perfectly for the last 5 years (about 5,000,000 calls) until the mutex was not acquired (which has happened only once - the other day).

GuiAcquireMutex() is called multiple times within a function (that calls many sub-functions - about 10,000 lines of code) called by Paint() to refresh the data required for Paint() to redraw 2 complex graphs. So PostCallback() would be rather difficult to implement.

I am probably being a little pedantic but I like my apps to fail gracefully rather than freeze for a minute before telling the user to restart it.

Thanks for your thoughts.

Re: Problem breaking loop (with close button) in main thread [message #59040 is a reply to message #59039] Wed, 19 October 2022 02:21 Go to previous messageGo to next message
Lance is currently offline  Lance
Messages: 526
Registered: March 2007
Contributor
Hi Awksed:

My bad. Your code goto again; only when WaitForSingleEvent fails. I was thinking in the context that it has been converted into a while loop. Smile

Quote:

Is there some way in U++ to allow the close button to call Close() while the main thread is looping
(like Windows PumpWaitingMessages())?

I would use ProcessEvents() in the loop. No idea how it will cause failure in your case. Maybe you can add a flag in Paint() to not call GuiAcquireMutex(..) again if there is a previous call not completed yet?

I would think the ProcessEvents should be inserted somewhere in your GuiAcquireMutex function (before jump back to again maybe), but it's somewhere else needs to be changed accordingly if problem arose because of it.

[Updated on: Wed, 19 October 2022 02:22]

Report message to a moderator

Re: Problem breaking loop (with close button) in main thread [message #59041 is a reply to message #59040] Wed, 19 October 2022 02:42 Go to previous messageGo to next message
Lance is currently offline  Lance
Messages: 526
Registered: March 2007
Contributor
In the loop (by goto, there is an implicit loop), if you call ProcessEvent, which gives chance for Paint() who will call this function one more time and stuck in the same [b]WaitForSingleEvent[b] failure, and it will call [b]ProcessEvents[b] in its waiting loop, ... it goes on until stack is exhausted. I might be wrong, but that's my gut's feeling why adding ProcessEvent doesn't work for you.

I would add some check like this:

DWORD GuiAcquireMutex(HANDLE hMutex, DWORD dwTimeout)
{
  static int inthecall;

  if( inthecall )
       return ERROR_IN_CALL; // define this value yourself
  ++inthecall;
  if(hMutex == INVALID_HANDLE_VALUE)
   return ERROR_INVALID_HANDLE;
 
 long long llNow;
 long long llStart        = GetMilliTime();
 long long llEnd          = llStart + (long long) dwTimeout;
 DWORD     dwShortTimeout = 1000;        // 1 second
 DWORD     dwLastError;
 DWORD     dwWaitResult;
   ....

Make sure you --inthecall; in all branches before leaving the function.
Re: Problem breaking loop (with close button) in main thread [message #59043 is a reply to message #59041] Wed, 19 October 2022 20:56 Go to previous messageGo to next message
awksed is currently offline  awksed
Messages: 61
Registered: April 2012
Member
Hi Lance,

As I said in my original post

Quote:

Adding Ctrl::ProcessEvents() causes a "WM_PAINT invoked ... while in Paint routine" error.


Looking at ProcessEvents() (it calls Windows PeekMessage) I tried PeekMessage with PM_REMOVE and also PM_NOREMOVE. This make things much worse (totally hung the app). Used as per Microsoft example it works ok for the first loops and subsequently loops continuously.

I guess the problem is calling ProcessEvents/PeekMessage from within Paint.

As the app not responding for a minute (showing the wait cursor) if a mutex is never acquired (while allowing the window to be dragged using the title bar) and then telling the user to restart the app is not so bad. Especially only once every 5 years Very Happy .

So I shall give up and stop spinning my wheels on this one.

Thanks again for your efforts.

Cheers.
Re: Problem breaking loop (with close button) in main thread [message #59044 is a reply to message #59043] Thu, 20 October 2022 01:04 Go to previous messageGo to next message
Lance is currently offline  Lance
Messages: 526
Registered: March 2007
Contributor
Hi Awksed

I am sorry to hear that you have to satisfy with a compromise.

I manage to create the following test. It runs as expected on Linux, but would not work on Windows.

Regards,
Lance

----------------
Correction: No, it's not a problem with ProcessEvents(). After I change the thread lambda to not log on GUI but to debug output file, it runs like a charm. It's interesting. I probably should not modify GUI from within other than the GUI thread. Seems Linux+GTK are more tolerant to this kind of error.
	void Start(){
		static int cnt;
		if( cnt!= 0 )
			return;
		++ cnt;
		stop_requested = false;
		log.Clear();
		badguy.Run([this]{
			LOG("Badguy running...\n"); // CHANGE THIS
			Mutex::Lock _(mut);
			LOG("Badguy now owns the Mutex.\n");  // CHANGE THIS
			while(!stop_requested && !IsShutdownThreads() ){
				Sleep(50);
			}
			LOG("Stop requested, Badguy exiting and releasing the Mutex...\n"); // AND THIS
		});

		// wait for badguy to acquire the mutex
		while(mut.TryEnter()){
			mut.Leave();
			ProcessEvents();
			Sleep(100);
		}

		log.Append("Trying to acquire the mutex...\n");
		GuiAcquireMutex();
		--cnt;
	}

[Updated on: Thu, 20 October 2022 13:09]

Report message to a moderator

Re: Problem breaking loop (with close button) in main thread [message #59048 is a reply to message #59044] Thu, 20 October 2022 20:10 Go to previous messageGo to next message
Oblivion is currently offline  Oblivion
Messages: 1091
Registered: August 2007
Senior Contributor
Hi Lance,

Quote:

probably should not modify GUI from within other than the GUI thread.


You are allowed to modify GUI from within other threads. For this purpose, you need to use

a) EnterGuiMutex() & LeaveGuiMutex();
b) GuiLock, which is basicly the above functions wrapped in a class for convenience:

Rudimentary example:

#include <CtrlLib/CtrlLib.h>

using namespace Upp;

#define LAYOUTFILE <DeadMutex/DeadMutex.lay>
#include <CtrlCore/lay.h>

static std::atomic<bool> fin;

class DeadMutex : public WithDeadMutexLayout<TopWindow> {
	
	typedef DeadMutex CLASSNAME;
	
public:
	DeadMutex()
	{
		CtrlLayout(*this, "Window title");
		start.WhenPush = THISFN(Start);
		stop.WhenPush = [this]{ fin = true; };
		WhenClose = [this]{ ShutdownThreads(); Break(); };
	}
	
	void Start(){
		Thread().Run([=] {
			{
				GuiLock __; // EnterGuiMutex()
				log.Append("Badguy instance running & owned the gui mutex...\n");
				// LeaveGuiMutex();
			}
			while(!fin && !IsShutdownThreads() ){
				Sleep(50);
			}
			GuiLock __;
			log.Append("Stop requested, Badguy instance exiting...\n");
			fin = false;
		});
	}
};

GUI_APP_MAIN
{
	DeadMutex().Run();
}



There is also a GuiUnlock class which basically reverses the order of LeaveGuiMutex and EnterGuiMutex, so you can force the main thread to temporarily unlock its GUI lock.

What you are not allowed to do is, creating windows and prompts within other threads.


Best regards,
Oblivion


[Updated on: Thu, 20 October 2022 20:12]

Report message to a moderator

Re: Problem breaking loop (with close button) in main thread [message #59049 is a reply to message #59048] Thu, 20 October 2022 21:49 Go to previous message
Lance is currently offline  Lance
Messages: 526
Registered: March 2007
Contributor
Great! Thank you very much, Oblivion!
Previous Topic: Dealing with background tasks elegantly in a userinterface
Next Topic: error / memory leak in HttpServer example
Goto Forum:
  


Current Time: Thu Mar 28 11:39:39 CET 2024

Total time taken to generate the page: 0.01408 seconds