calling MainMenuAction() from an other plugin

Message ID 4429A484.7020901@gmx.de
State New
Headers

Commit Message

Reinhard Nissl March 28, 2006, 9:03 p.m. UTC
  Hi,

Udo Richter 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.

Bye.
  

Comments

Klaus Schmidinger March 28, 2006, 9:21 p.m. UTC | #1
Reinhard Nissl wrote:
> Hi,
> 
> Udo Richter 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?

Klaus
  
Reinhard Nissl March 28, 2006, 9:47 p.m. UTC | #2
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.

Bye.
  
Udo Richter March 28, 2006, 10:04 p.m. UTC | #3
Klaus Schmidinger wrote:
> Am I missing something here?

This is mostly about plugins that expect their MainMenuAction to be 
called. A plugin may catch this call and display a message instead of 
the default menu. If the call is canceled, the message will unexpectedly 
appear the next time the user selects the main menu item.
(As I said before, I would suggest signaling and timeouts anyway)

Things might get useful with another patch I wouldn't dare to suggest 
before 1.4 ;)

This is current code:

    DELETE_MENU;
    if (cControl::Control())
       cControl::Control()->Hide();
    cPlugin *plugin = cPluginManager::GetPlugin(cRemote::GetPlugin());
    if (plugin) {
       Menu = plugin->MainMenuAction();
       if (Menu)
          Menu->Show();
       }

This is my idea:

    cPlugin *plugin = cPluginManager::GetPlugin(cRemote::GetPlugin());
    if (plugin) {
       cMenu *NewMenu = plugin->MainMenuAction();
       if (NewMenu) {
          DELETE_MENU;
          if (cControl::Control())
             cControl::Control()->Hide();
          Menu = NewMenu;
          Menu->Show();
          }
       }

That way, the old OSD/menu will be destroyed only if MainMenuAction 
returned a new menu. If MainMenuAction returns NULL, nothing should 
happen. This would be a nice dirty trick to catch the main thread from a 
plugin.
I don't know whether that change has bad side effects though.

Cheers,

Udo
  

Patch

--- ../vdr-1.3.44-orig/remote.h	2006-01-29 13:27:08.000000000 +0100
+++ remote.h	2006-03-26 14:48:15.000000000 +0200
@@ -29,6 +29,8 @@  private:
   static cMutex mutex;
   static cCondVar keyPressed;
   static const char *plugin;
+  static const char *plugins[MaxKeys];
+  static int pluginCount;
   char *name;
 protected:
   cRemote(const char *Name);
@@ -45,7 +47,7 @@  public:
   static void Clear(void);
   static bool Put(eKeys Key, bool AtFront = false);
   static bool PutMacro(eKeys Key);
-  static void CallPlugin(const char *Plugin);
+  static bool CallPlugin(const char *Plugin);
       ///< Initiates calling the given plugin's main menu function.
       ///< The Plugin parameter is the name of the plugin, and must be
       ///< a static string.
--- ../vdr-1.3.44-orig/remote.c	2006-01-29 15:43:07.000000000 +0100
+++ remote.c	2006-03-26 14:46:20.000000000 +0200
@@ -30,6 +30,8 @@  char *cRemote::unknownCode = NULL;
 cMutex cRemote::mutex;
 cCondVar cRemote::keyPressed;
 const char *cRemote::plugin = NULL;
+const char *cRemote::plugins[MaxKeys];
+int cRemote::pluginCount = 0;
 
 cRemote::cRemote(const char *Name)
 {
@@ -67,6 +69,8 @@  void cRemote::Clear(void)
 {
   cMutexLock MutexLock(&mutex);
   in = out = 0;
+  plugin = NULL;
+  pluginCount = 0;
   if (learning) {
      free(unknownCode);
      unknownCode = NULL;
@@ -145,10 +149,14 @@  bool cRemote::Put(const char *Code, bool
   return false;
 }
 
-void cRemote::CallPlugin(const char *Plugin)
+bool cRemote::CallPlugin(const char *Plugin)
 {
-  plugin = Plugin;
-  Put(k_Plugin);
+  cMutexLock MutexLock(&mutex);
+  if (Put(k_Plugin)) {
+     plugins[pluginCount++] = Plugin;
+     return true;
+     }
+  return false;
 }
 
 bool cRemote::HasKeys(void)
@@ -167,6 +175,10 @@  eKeys cRemote::Get(int WaitMs, char **Un
             out = 0;
          if ((k & k_Repeat) != 0)
             repeatTimeout.Set(REPEATTIMEOUT);
+         if (k == k_Plugin && pluginCount > 0) {
+            plugin = plugins[0];
+            memmove(plugins, plugins + 1, sizeof(*plugins) * --pluginCount);
+            }
          return k;
          }
       else if (!WaitMs || !keyPressed.TimedWait(mutex, WaitMs) && repeatTimeout.TimedOut()) {