Debugger says: You MUST NOT NEVER EVER use a modal dialog in a timer



  • Hello,

    in a MessageData plugin, I'm listening to a specific core message (sent from another part of my plugin). When processing that message in MessageData::CoreMessage(), I need to open a modal dialog (GeModalDialog) to get user credentials.

    Everything works perfectly. However, when that dialog opens, I get this message in the debugger:

    You MUST NOT NEVER EVER use a modal dialog in a timer. Please run a job on the main thread (see job.h).
    

    Even though everything behaves as expected, I decided to take the advice and open the dialog on the main thread. This is how I'm doing it:

    Bool GetUserCredentials(String &username, String &password)
    {
    	// CredentialsDialog is a GeModalDialog derivative
    	CredentialsDialog credentialsDialog;
    	credentialsDialog.Open();
    	if (credentialsDialog.GetResult())
    	{
    		credentialsDialog.GetCredentials(username, password);
    		return true;
    	}
    
    	return false;
    }
    
    ...
    
    // Ask the user for credentials, so we can acquire a new access token
    String username, password;
    auto getUserCredentials = [&username, &password] () -> Bool
    {
    	// Open the dialog and get the credentials the user has typed in
    	return GetUserCredentials(username, password);
    };
    
    if (maxon::ExecuteOnMainThread(getUserCredentials, true))
    {
    	// Do something useful
    	// ...
    

    Well, it still works as expected, but I am also still getting that message in the debugger. What could be wrong?

    Thanks for any hints & advice!

    Cheers,
    Frank



  • Hi,

    this is pure speculation, but I would assume that this has something to do with the fact that both GeDialog and MessageData implement a timer. Have you tried turning off the timer functionality of your credentialsDialog with credentialsDialog.SetTimer(0) before opening it (assuming your dialog does not rely on its timer in some way)?

    The warning is a bit cryptic, but I would interpret it in such way, that you cannot open a new timer running on the main thread before any previous timers have closed. Your MessageData plugin opens one and before the scope of that timer has been left, you instantiate a modal dialog, which interferes with the timer ticks of the that running MessageData timer. That would at least explain all symptoms.

    There probably is not an easy way out of this when turning of the timer of your dialog does not work other than not using a modal dialog (because you cannot escape the context of the MessageData timer).

    Cheers,
    zipit



  • thanks for your reply!

    @zipit said in Debugger says: You MUST NOT NEVER EVER use a modal dialog in a timer:

    There probably is not an easy way out of this when turning of the timer of your dialog does not work other than not using a modal dialog (because you cannot escape the context of the MessageData timer).

    I was under the impression that maxon::ExecuteOnMainThread() was doing exactly this. Maybe it's a bogus warning?
    Would be nice to have the SDK team say something about this ;-)

    Cheers,
    Frank



  • @zipit said in Debugger says: You MUST NOT NEVER EVER use a modal dialog in a timer:

    Have you tried turning off the timer functionality of your credentialsDialog with credentialsDialog.SetTimer(0) before opening it (assuming your dialog does not rely on its timer in some way)?

    Just tried it, but it changes nothing. Everything still works fine, and the warning is still there ;-)
    But thank you!

    P.S.: I just notice that the message actually does sound like a joke... "You MUST NOT NEVER EVER" is a double negative, so it is in fact encouraging the use of model dialogs in a timer :-D



  • I just notice that the message actually does sound like a joke... "You MUST NOT NEVER EVER" is a double negative, so it is in fact encouraging the use of model dialogs in a timer :-D

    Lol, jeah, I noticed that too, also the capslock part gives it probably an unintended comedic connotation.



  • hello,

    Sorry for the delay i had to ask the devs.

    The old Events, like the CoreMessage are executed furing certain OS input events and periodically during the timer.
    So that's why that message is triggered inside a timer and you got the Message.

    "Is it so bad ? "

    Timer Events for all subscribers are blocked.
    Further Event (nested) are blocked.

    It could be bad if you are sending messages and are waiting for an answers.

    To avoid that you can enqueue a job to the main thread.
    ExecuteOnMainThread doesn't help here because it directly execute the lambda if you are already in the main thread.

    Cheers,
    Manuel



  • Thank you, Manuel!



  • Hmm, any hints on how one might do this?
    I looked at the Jobs Manual in the SDK, but didn't quite get it.

    I guess it would work somehow like this:

    struct Credentials
    {
    	String username;
    	String password;
    };
    
    // Ask the user for credentials, so we can acquire a new access token
    class GetCredentialsJob : public maxon::JobInterfaceTemplate<GetCredentialsJob, Credentials>
    {
    public:
    	GetCredentialsJob() { };
    
    	// function operator with the actual workload
    	maxon::Result<void> operator ()()
    	{
    		Credentials credentials;
    		GetUserCredentials(credentials.username, credentials.password);
    
    		// store result
    		return SetResult(credentials); // ERROR: Rvalue reference to type 'Credentials' cannot bind to lvalue of type 'Credentials'
    	}
    };
    
    auto job = GetCredentialsJob::Create();
    job.Enqueue(maxon::JobQueueInterface::GetMainThreadQueue()); // ERROR: No member named 'Enqueue' in ...
    
    // Wait for job to finish, then somehow get the results and do something
    if (job.Wait()) // ERROR: No member named 'Wait' in ...
    {
    	Credentials credentials = job.GetResult(); // ERROR: No member named 'GetResult' in ...
    	// ...
    

    This seems like an awful lot of code, just to open a simple dialog and get two strings from it. And since it throws a number of errors, it's not even finished yet.



  • hello,

    you can pass a lambda function directly as suggest other enqueue method definitions

    That should be a bit less code.

    maxon::JobRef::Enqueue(
    	[]() -> maxon::Result<void>
    	{
    			String res = "temporary";
    		if (!RenameDialog(&res))
    			return maxon::UnknownError(MAXON_SOURCE_LOCATION);
    		ApplicationOutput("you new password is @", res);
    		return maxon::OK;
    	}
    	, maxon::JobQueueRef::GetMainThreadQueue()) iferr_return;
    
    	
    

    You can send a message on your lambda that will update your plugins (i don't know how you are handling your licences).

    Cheers,
    Manuel



  • Ah, great! Yes, that looks a lot more compact, thank you!



  • Hmm, referring to your code example, I am doing it like this now:

    // Ask the user for credentials, so we can acquire a new access token
    String username, password;
    Bool dialogResult;
    
    maxon::JobRef ref = maxon::JobRef::Enqueue(
    			[&username, &password, &dialogResult] () -> Bool
    			{
    				dialogResult = GetUserCredentials(username, password);
    				return dialogResult;
    			}, maxon::JobQueueRef::GetMainThreadQueue()) iferr_return;
    
    // Wait until dialog is closed
    ref.Wait(); 
    
    // If user clicked "OK"
    if (dialogResult)
    {
    	// do stuff
    	// ...
    

    It does not help, sorry.

    1. Cinema freezes and never wakes up again. The dialog does not appear.
    2. If I don't do the ref.Wait(), Cinema does not freeze, but it also does not wait for the user to close the dialog.
    3. How do I even return the result of GetUserCredentials() ? The lambda has Bool as return type, but JobRef::GetResult() only returns a maxon::Result<void>. Therefore, I'm returning the value via a reference now.
    4. Worst of all, the warning You MUST NOT NEVER EVER use a modal dialog in a timer. Please run a job on the main thread (see job.h). still appears!!

    Is there anything else I can try?

    Thanks & greetings,
    Frank



  • hi,

    why you don't put the "todo stuff" inside your lambda ?
    here, my function pc12545 is called by a command Data.

    class pc12545_nodalDialog : public GeModalDialog
    {
    
    public:
    	Bool CreateLayout(void) override
    	{
    		AddEditText(1000, BFH_LEFT, SizePix(150));
    		AddButton(1001, BFH_LEFT, 50, 20, "OK"_s);
    		return true;
    	}
    
    
    	Bool InitValues(void) override
    	{
    		SetString(1000, "temporary"_s);
    		return true;
    	}
    
    
    	Bool AskClose(void) override
    	{
    		return false;
    	}
    
    
    	Bool Command(Int32 id, const BaseContainer& msg) override
    	{
    
    		if (id == 1001)
    		{
    			GetString(1000, _password);
    			if (_password.IsPopulated())
    				Close(true);
    			else
    				MessageDialog("Please Enter A password"_s);
    		}
    
    		return true;
    	}
    
    	maxon::Result<maxon::String> GetPass() const
    	{
    		if (_password.IsPopulated())
    			return _password;
    
    		return maxon::UnknownError(MAXON_SOURCE_LOCATION);
    	}
    
    private:
    	maxon::String _password = ""_s;
    
    };
    
    
    static maxon::Result<void> pc12545(BaseDocument* doc)
    {
    	iferr_scope;
    
    	
     maxon::JobRef::Enqueue(
    	[]() -> maxon::Result<void>
    	{
    		 iferr_scope;
    			pc12545_nodalDialog credentialsDialog;
    			credentialsDialog.Open(-1, -1, 200, 200, true);
    
    			if (credentialsDialog.GetResult())
    			{
    				const	maxon::String returnedString = credentialsDialog.GetPass() iferr_return;
    				ApplicationOutput("you new password is @", returnedString);
    				
    			}
    			else
    			{
    				ApplicationOutput("your password haven't been modified");
    				return maxon::UnknownError(MAXON_SOURCE_LOCATION);
    			}
                            // ... do something with the password.CoreMessage maybe.
    			return maxon::OK;
    			
    	}
    	, maxon::JobQueueRef::GetMainThreadQueue()) iferr_return;
    
    	
    
    	return maxon::OK;
    }
    

    Cheers,
    Manuel



  • This post is deleted!


  • Hmm, I can do the "to do" stuff inside the lambda, of course. But what would that change? The problem is, still, that the code is being called from MessageData::CoreMessage() (inside a CommandData, like in your example, was never a problem), and therefore opening the dialog causes the warning to appear, no matter what code comes after opening it.

    And the other problem still persists, too: If I don't Wait() after enqueueing the lambda, the dialog doesn't even appear. And if I Wait(), Cinema freezes.

    Maybe it's simply not possible to do anything with modal dialogs in code that's called from MessageData::CoreMessage, regardless of any threading stunts we could do?



  • hi,
    using wait or getresult is really strange i agree, but you don't need them.

    I've added a MessageData and use a timer to trigger the CoreMessage. There i enqueue de job that is executed immediately,

    The following code is not executed because g_inTimer is false. (the job is executed from main thread).

    	if (g_inTimer)
    	{
    		DiagnosticOutput("You MUST NOT NEVER EVER use a modal dialog in a timer. Please run a job on the main thread (see job.h).");
    	}
    

    the MessageData i've added :

    class TimerMessage_pc12545 : public MessageData
    {
    	virtual Int32 GetTimer(void);
    	virtual Bool CoreMessage(Int32 id, const BaseContainer &bc);
    };
    
    Int32 TimerMessage_pc12545::GetTimer()
    {
    	return 10000;
    }
    
    Bool TimerMessage_pc12545::CoreMessage(Int32 id, const BaseContainer &bc)
    {
    	if (id == MSG_TIMER)
    	{
    		iferr_scope_handler
    		{
    			return false;
    
    		};
    
    		maxon::JobRef::Enqueue(
    			[]() -> maxon::Result<void>
    			{
    				iferr_scope;
    				pc12545_nodalDialog credentialsDialog;
    				credentialsDialog.Open(-1, -1, 200, 200, true);
    
    				if (credentialsDialog.GetResult())
    				{
    					const	maxon::String returnedString = credentialsDialog.GetPass() iferr_return;
    					ApplicationOutput("you new password is @", returnedString);
    
    				}
    				else
    				{
    					ApplicationOutput("your password haven't been modified");
    					return maxon::UnknownError(MAXON_SOURCE_LOCATION);
    				}
    				return maxon::OK;
    				// ... do something with the password.
    			}
    		, maxon::JobQueueRef::GetMainThreadQueue()) iferr_return;
    
    
    	}
    
    	return true;
    }
    

    Cheers,
    Manuel



  • Hm, thanks. Well, in my case, I don't even use the MessageData's timer. I am simply reacting to a CoreMessage that is sent from another part of the plugin. And it seems, in that case g_inTimer is true. Is there a way to make it false?

    My code looks basically similar to yours, I'm just listening to another message, not MSG_TIMER.



  • Addition: I've also tried returning 0 from MessageData::GetTimer(), it changes nothing.



  • And 2nd addition: How do you proceed when you actually need the credentials from the dialog in the code after the lambda? I can't move the whole plugin with all its components to a separate enqueued job, just because of the problem with modal dialogs.

    I need to show the user the dialog, and get their input. I need things to wait until the user has closed the dialog, then I need the credentials and do stuff with it that is not part of the same lambda.



  • hi,
    I don't think the message i react to is important.

    You can call whatever function you need inside the lambda. You can pass that function some parameters. This will be executed from the mainThread.
    Once it's done, the lambda will send a CoreMessage and your plugins could react to that.

    it's just like "hey job, do this for me and tell me once it's done"

    Sorry i don't see where's the issu here, or at least i can't reproduce it.
    Maybe you could send us some more information by email to keep it secret (as it's related to password etc) or code.

    Cheers,
    Manuel



  • Sending another CoreMessage... interesting... I will try that, too. Thank you!