calling MainMenuAction() from an other plugin

Message ID 444361E1.3060302@cadsoft.de
State New
Headers

Commit Message

Klaus Schmidinger April 17, 2006, 9:37 a.m. UTC
  Reinhard Nissl wrote:
> Hi,
> 
> Klaus Schmidinger wrote:
> 
>>>>>> if (ShowMessage && !Skins.IsOpen() && !cOsd::IsOpen()) {
>>>>>>    ShowMessage = false;
>>>>>>    cRemote::CallPlugin("myShowMessage");
>>>>>>    }
>>>>>
>>>>>
>>>>>
>>>>> this looks racy to me. What if two different threads do this?
>>>>> The OSD could open between the if () and ::CallPlugin
>>>>
>>>>
>>>>
>>>> CallPlugin is always racy, as only one plugin call will be remembered.
>>>>
>>>> The only safe way is to set up a signal and wait for MainMenuAction 
>>>> being called. If it is not called within timeout, try again.
>>>>
>>>> The IsOpen() calls should ideally be made from foreground thread, 
>>>> but usually there's no way to get there. Testing from within 
>>>> MainMenuAction is pointless, as OSD and menu get closed right before 
>>>> MainMenuAction.
>>>
>>>
>>>
>>> I did something similar in my vdr-xine plugin. Things could be 
>>> improved using the attached patch.
>>>
>>> It allows to remember up to 16 calls to different plugins and the 
>>> caller can detect, whether putting the call in the remote key fifo 
>>> was successful.
>>
>>
>> Just so I get this right: with your patch it will be possible
>> to put several plugin calls into the key queue. Let's assume that
>> there are 3 plugin calls currently in the queue. When VDR goes through
>> the next main loop, it will encounter a k_Plugin in the queue and call
>> the first plugin's MainMenuAction(). Since there is more input in the
>> queue, that plugin's OSD won't be displayed, yet (that's only
>> done in cInterface::GetKey() in case the queue is empty). So it takes
>> the next k_Plugin out of the queue, deletes the menu it just created
>> and call the second plugin's MainMenuAction(). Before displaying that one
>> it will get the third k_Plugin out of the queue, call that plugin's
>> MainMenuAction(), and that's what will be finally displayed.
>>
>> Unless I'm missing something here, queueing (up to 16) k_Plugins
>> doesn't have any different result (from the user's point of view)
>> than just overwriting any previously stored plugin call. The only thing
>> that appears to be important is the (currently missing) mutex lock
>> in cRemote::CallPlugin().
>>
>> Am I missing something here?
> 
> 
> In my case, I don't show an OSD. I just use this functionality as a 
> trampoline to have the VDR main thread execute my code for switching the 
> primary device, as it doesn't work reliably when it is done in any other 
> thread.
> 
> So you are right, when a plugin opens an OSD (which is the typical 
> case), one will only see the OSD of the last plugin. On the other hand, 
> it would be useful to know for the caller, that the MenuMenuAction of 
> the specified plugin will be called when CallPlugin() returned true. 
> Otherwise it would need more "intelligent" code at the caller to achieve 
> the call under race conditions.
> 
> In the case where the above is of no interest, there is no need to have 
> an additional mutex lock in CallPlugin(), as Put() has one in remote.c:79.

I'm not particularly fond of that FIFO of yours.
However, I do realize that it is useful to tell the caller of
cRemote::CallPlugin() whether the call was successful.
The attached patch vdr-1.3.46-callplugin.diff makes cRemote::CallPlugin()
return false if there is currently a plugin call pending.

The ability to "catch the main thread" will be implemented by the
second attached patch (vdr-1.3.46-mainthreadhook.diff), so that no
"dirty tricks" should be necessary. This patch may have its line
numbers a little off, because I have already made other changes
to these files, but I wanted to give you a chance to look at this
and maybe comment on it before I release version 1.3.47 later today.

Klaus
  

Comments

Reinhard Nissl April 17, 2006, 2:28 p.m. UTC | #1
Hi,

Klaus Schmidinger wrote:

>> In my case, I don't show an OSD. I just use this functionality as a 
>> trampoline to have the VDR main thread execute my code for switching 
>> the primary device, as it doesn't work reliably when it is done in any 
>> other thread.
>>
>> So you are right, when a plugin opens an OSD (which is the typical 
>> case), one will only see the OSD of the last plugin. On the other 
>> hand, it would be useful to know for the caller, that the 
>> MenuMenuAction of the specified plugin will be called when 
>> CallPlugin() returned true. Otherwise it would need more "intelligent" 
>> code at the caller to achieve the call under race conditions.
>>
>> In the case where the above is of no interest, there is no need to 
>> have an additional mutex lock in CallPlugin(), as Put() has one in 
>> remote.c:79.
> 
> I'm not particularly fond of that FIFO of yours.
> However, I do realize that it is useful to tell the caller of
> cRemote::CallPlugin() whether the call was successful.
> The attached patch vdr-1.3.46-callplugin.diff makes cRemote::CallPlugin()
> return false if there is currently a plugin call pending.

The code looks good to me. Am I right that CallPlugin() shall now only 
be used to open the plugins main menu, i. e. no longer any other 
processing in the context of the main thread?

> The ability to "catch the main thread" will be implemented by the
> second attached patch (vdr-1.3.46-mainthreadhook.diff), so that no
> "dirty tricks" should be necessary. This patch may have its line
> numbers a little off, because I have already made other changes
> to these files, but I wanted to give you a chance to look at this
> and maybe comment on it before I release version 1.3.47 later today.

I assume that this new interface function should be used for the code 
which has nothing to do with the plugins main menu but was put in that 
MainMenuAction() to execute the code in the context of the main thread.

In my case, the following code is to be executed in the new function:

   void cXineDevice::mainMenuTrampoline()
   {
#if VDRVERSNUM >= 10332
     cMutexLock switchPrimaryDeviceLock(&m_switchPrimaryDeviceMutex);
     if (m_switchPrimaryDeviceDeviceNo < 0)
       return;

     cControl::Shutdown();

     if (m_switchPrimaryDeviceDeviceNo == (1 + DeviceNumber()))
     {
       char *msg = 0;
       ::asprintf(&msg, tr("Switching primary DVB to %s..."), 
m_plugin->Name());

       Skins.Message(mtInfo, msg);
       ::free(msg);
     }

     SetPrimaryDevice(m_switchPrimaryDeviceDeviceNo);

     if (m_switchPrimaryDeviceDeviceNo != (1 + DeviceNumber()))
     {
       char *msg = 0;
       ::asprintf(&msg, tr("Switched primary DVB back from %s"), 
m_plugin->Name());

       Skins.Message(mtInfo, msg);
       ::free(msg);
     }

     m_switchPrimaryDeviceCond.Broadcast();
#endif
   }

I see here a new problem, as I need to call cControl::Shutdown(), but 
when VDR's main thread returns to it's main loop, it still may use 
"Menu" which is most likely invalid at that time:

         // Main thread hooks of plugins:
         PluginManager.MainThreadHook();
         // User Input:
         cOsdObject *Interact = Menu ? Menu : cControl::Control();

Maybe PluginManager.MainThreadHook() should be called earlier.

Bye.
  
Udo Richter April 17, 2006, 2:43 p.m. UTC | #2
Klaus Schmidinger wrote:
> The ability to "catch the main thread" will be implemented by the
> second attached patch (vdr-1.3.46-mainthreadhook.diff), so that no
> "dirty tricks" should be necessary.
> 
> --- plugin.h	2005/08/27 16:13:17	1.10
> +++ plugin.h	2006/04/17 09:18:16
> @@ -39,6 +39,7 @@
>    virtual bool Start(void);
>    virtual void Stop(void);
>    virtual void Housekeeping(void);
> +  virtual void MainThreadHook(void);
>  

Nice thing to have, but while we're at it, what about a way to open the 
OSD from there? That way we could get rid of the CallPlugin / 
MainMenuAction hacks altogether. Just allow cOsdObject* as return type.

Of course this wont solve the races between plugins. Only one can be 
shown in one pass, the rest must be destroyed.

Cheers,

Udo
  
Klaus Schmidinger April 17, 2006, 2:47 p.m. UTC | #3
Reinhard Nissl wrote:
> Hi,
> 
> Klaus Schmidinger wrote:
> 
>>> In my case, I don't show an OSD. I just use this functionality as a 
>>> trampoline to have the VDR main thread execute my code for switching 
>>> the primary device, as it doesn't work reliably when it is done in 
>>> any other thread.
>>>
>>> So you are right, when a plugin opens an OSD (which is the typical 
>>> case), one will only see the OSD of the last plugin. On the other 
>>> hand, it would be useful to know for the caller, that the 
>>> MenuMenuAction of the specified plugin will be called when 
>>> CallPlugin() returned true. Otherwise it would need more 
>>> "intelligent" code at the caller to achieve the call under race 
>>> conditions.
>>>
>>> In the case where the above is of no interest, there is no need to 
>>> have an additional mutex lock in CallPlugin(), as Put() has one in 
>>> remote.c:79.
>>
>>
>> I'm not particularly fond of that FIFO of yours.
>> However, I do realize that it is useful to tell the caller of
>> cRemote::CallPlugin() whether the call was successful.
>> The attached patch vdr-1.3.46-callplugin.diff makes cRemote::CallPlugin()
>> return false if there is currently a plugin call pending.
> 
> 
> The code looks good to me. Am I right that CallPlugin() shall now only 
> be used to open the plugins main menu, i. e. no longer any other 
> processing in the context of the main thread?
> 
>> The ability to "catch the main thread" will be implemented by the
>> second attached patch (vdr-1.3.46-mainthreadhook.diff), so that no
>> "dirty tricks" should be necessary. This patch may have its line
>> numbers a little off, because I have already made other changes
>> to these files, but I wanted to give you a chance to look at this
>> and maybe comment on it before I release version 1.3.47 later today.
> 
> 
> I assume that this new interface function should be used for the code 
> which has nothing to do with the plugins main menu but was put in that 
> MainMenuAction() to execute the code in the context of the main thread.

Right. That was a dirty trick and I didn't want to manifest that ;-)

> In my case, the following code is to be executed in the new function:
> 
>   void cXineDevice::mainMenuTrampoline()
>   {
> #if VDRVERSNUM >= 10332
>     cMutexLock switchPrimaryDeviceLock(&m_switchPrimaryDeviceMutex);
>     if (m_switchPrimaryDeviceDeviceNo < 0)
>       return;
> 
>     cControl::Shutdown();
> 
>     if (m_switchPrimaryDeviceDeviceNo == (1 + DeviceNumber()))
>     {
>       char *msg = 0;
>       ::asprintf(&msg, tr("Switching primary DVB to %s..."), 
> m_plugin->Name());
> 
>       Skins.Message(mtInfo, msg);
>       ::free(msg);
>     }
> 
>     SetPrimaryDevice(m_switchPrimaryDeviceDeviceNo);
> 
>     if (m_switchPrimaryDeviceDeviceNo != (1 + DeviceNumber()))
>     {
>       char *msg = 0;
>       ::asprintf(&msg, tr("Switched primary DVB back from %s"), 
> m_plugin->Name());
> 
>       Skins.Message(mtInfo, msg);
>       ::free(msg);
>     }
> 
>     m_switchPrimaryDeviceCond.Broadcast();
> #endif
>   }
> 
> I see here a new problem, as I need to call cControl::Shutdown(), but 
> when VDR's main thread returns to it's main loop, it still may use 
> "Menu" which is most likely invalid at that time:
> 
>         // Main thread hooks of plugins:
>         PluginManager.MainThreadHook();
>         // User Input:
>         cOsdObject *Interact = Menu ? Menu : cControl::Control();
> 
> Maybe PluginManager.MainThreadHook() should be called earlier.

Originally I was thinking about actually putting it further down.
How about we put it to the very end of the "while (!Interrupted) {"
loop? That way it shouldn't interfere with anything.

I'll make it that way for version 1.3.47, which I will be releasing
shortly.

Klaus
  
Klaus Schmidinger April 17, 2006, 2:51 p.m. UTC | #4
Udo Richter wrote:
> Klaus Schmidinger wrote:
> 
>> The ability to "catch the main thread" will be implemented by the
>> second attached patch (vdr-1.3.46-mainthreadhook.diff), so that no
>> "dirty tricks" should be necessary.
>>
>> --- plugin.h    2005/08/27 16:13:17    1.10
>> +++ plugin.h    2006/04/17 09:18:16
>> @@ -39,6 +39,7 @@
>>    virtual bool Start(void);
>>    virtual void Stop(void);
>>    virtual void Housekeeping(void);
>> +  virtual void MainThreadHook(void);
>>  
> 
> 
> Nice thing to have, but while we're at it, what about a way to open the 
> OSD from there? That way we could get rid of the CallPlugin / 
> MainMenuAction hacks altogether. Just allow cOsdObject* as return type.
> 
> Of course this wont solve the races between plugins. Only one can be 
> shown in one pass, the rest must be destroyed.

Let's not get carried away ;-)

This was a quick way of allowing a plugin to do something
in the main thread context that otherwise couldn't be done.

Now I want to wrap things up for the final approach to version 1.4.

Klaus
  
Reinhard Nissl April 17, 2006, 2:55 p.m. UTC | #5
Hi,

Klaus Schmidinger wrote:

>>>> In my case, I don't show an OSD. I just use this functionality as a 
>>>> trampoline to have the VDR main thread execute my code for switching 
>>>> the primary device, as it doesn't work reliably when it is done in 
>>>> any other thread.
>>>>
>>>> So you are right, when a plugin opens an OSD (which is the typical 
>>>> case), one will only see the OSD of the last plugin. On the other 
>>>> hand, it would be useful to know for the caller, that the 
>>>> MenuMenuAction of the specified plugin will be called when 
>>>> CallPlugin() returned true. Otherwise it would need more 
>>>> "intelligent" code at the caller to achieve the call under race 
>>>> conditions.
>>>>
>>>> In the case where the above is of no interest, there is no need to 
>>>> have an additional mutex lock in CallPlugin(), as Put() has one in 
>>>> remote.c:79.
>>>
>>> I'm not particularly fond of that FIFO of yours.
>>> However, I do realize that it is useful to tell the caller of
>>> cRemote::CallPlugin() whether the call was successful.
>>> The attached patch vdr-1.3.46-callplugin.diff makes 
>>> cRemote::CallPlugin()
>>> return false if there is currently a plugin call pending.
>>
>> The code looks good to me. Am I right that CallPlugin() shall now only 
>> be used to open the plugins main menu, i. e. no longer any other 
>> processing in the context of the main thread?
>>
>>> The ability to "catch the main thread" will be implemented by the
>>> second attached patch (vdr-1.3.46-mainthreadhook.diff), so that no
>>> "dirty tricks" should be necessary. This patch may have its line
>>> numbers a little off, because I have already made other changes
>>> to these files, but I wanted to give you a chance to look at this
>>> and maybe comment on it before I release version 1.3.47 later today.
>>
>> I assume that this new interface function should be used for the code 
>> which has nothing to do with the plugins main menu but was put in that 
>> MainMenuAction() to execute the code in the context of the main thread.
> 
> Right. That was a dirty trick and I didn't want to manifest that ;-)
> 
>> In my case, the following code is to be executed in the new function:
>>
>>   void cXineDevice::mainMenuTrampoline()
>>   {
>> #if VDRVERSNUM >= 10332
>>     cMutexLock switchPrimaryDeviceLock(&m_switchPrimaryDeviceMutex);
>>     if (m_switchPrimaryDeviceDeviceNo < 0)
>>       return;
>>
>>     cControl::Shutdown();
>>
>>     if (m_switchPrimaryDeviceDeviceNo == (1 + DeviceNumber()))
>>     {
>>
>> I see here a new problem, as I need to call cControl::Shutdown(), but 
>> when VDR's main thread returns to it's main loop, it still may use 
>> "Menu" which is most likely invalid at that time:
>>
>>         // Main thread hooks of plugins:
>>         PluginManager.MainThreadHook();
>>         // User Input:
>>         cOsdObject *Interact = Menu ? Menu : cControl::Control();
>>
>> Maybe PluginManager.MainThreadHook() should be called earlier.
> 
> Originally I was thinking about actually putting it further down.
> How about we put it to the very end of the "while (!Interrupted) {"
> loop? That way it shouldn't interfere with anything.

Should be ok too. It's similar to call it earlier in the next loop run ;-)

Bye.
  
kafifi April 17, 2006, 4:05 p.m. UTC | #6
Hello Klaus,

>Except for a few version numbers in the man pages etc., this
>is pretty much what will become the final version 1.4.

Sounds good ! Do you plan to add "enAIO patch" in final 1.4 ?


PS: 
At the end of vdr's description, could you please indicate 
what versions you're using :
- Driver version (kernel 2.6.1x.y or CVS-xxxx).
- Firmware version.  


Many thanks !
Karim.
  
Klaus Schmidinger April 22, 2006, 8:56 a.m. UTC | #7
kafifi wrote:
> Hello Klaus,
> 
> 
>>Except for a few version numbers in the man pages etc., this
>>is pretty much what will become the final version 1.4.
> 
> 
> Sounds good ! Do you plan to add "enAIO patch" in final 1.4 ?

No. There will be no more big changes for version 1.4.

> PS: 
> At the end of vdr's description, could you please indicate 
> what versions you're using :
> - Driver version (kernel 2.6.1x.y or CVS-xxxx).
> - Firmware version.  

I'll see what I can do.

Klaus
  
Patrick Gleichmann April 23, 2006, 11:59 a.m. UTC | #8
( don't use "reply" if you want to create a new mail thread )
  

Patch

--- plugin.h	2005/08/27 16:13:17	1.10
+++ plugin.h	2006/04/17 09:18:16
@@ -39,6 +39,7 @@ 
   virtual bool Start(void);
   virtual void Stop(void);
   virtual void Housekeeping(void);
+  virtual void MainThreadHook(void);
 
   virtual const char *MainMenuEntry(void);
   virtual cOsdObject *MainMenuAction(void);
@@ -89,6 +91,7 @@ 
   bool InitializePlugins(void);
   bool StartPlugins(void);
   void Housekeeping(void);
+  void MainThreadHook(void);
   static bool HasPlugins(void);
   static cPlugin *GetPlugin(int Index);
   static cPlugin *GetPlugin(const char *Name);
--- plugin.c	2006/04/09 14:16:17	1.18
+++ plugin.c	2006/04/17 09:20:05
@@ -69,6 +70,10 @@ 
 {
 }
 
+void cPlugin::MainThreadHook(void)
+{
+}
+
 const char *cPlugin::MainMenuEntry(void)
 {
   return NULL;
@@ -364,6 +374,15 @@ 
      }
 }
 
+void cPluginManager::MainThreadHook(void)
+{
+  for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) {
+      cPlugin *p = dll->Plugin();
+      if (p)
+         p->MainThreadHook();
+      }
+}
+
 bool cPluginManager::HasPlugins(void)
 {
   return pluginManager && pluginManager->dlls.Count();
--- vdr.c	2006/04/09 12:22:46	1.254
+++ vdr.c	2006/04/17 09:23:23
@@ -830,6 +830,8 @@ 
         // Queued messages:
         if (!Skins.IsOpen())
            Skins.ProcessQueuedMessages();
+        // Main thread hooks of plugins:
+        PluginManager.MainThreadHook();
         // User Input:
         cOsdObject *Interact = Menu ? Menu : cControl::Control();
         eKeys key = Interface->GetKey((!Interact || !Interact->NeedsFastResponse()) && time(NULL) - LastCamMenu > LASTCAMMENUTIMEOUT);