Call for tester for 1.4.0 features and fixes (1 Viewer)

Status
Not open for further replies.

Scythe42

Retired Team Member
  • Premium Supporter
  • June 20, 2009
    2,065
    2,703
    51
    Berlin
    Home Country
    Germany Germany
    @Developers: This brings up a C# question: is using Thread here OK or should I try to use ThreadPool? e.g. ThreadPool.QueueUserWorkItem(unused => Whatever());

    Code in question:

    Code:
    var workerThreads = new List<Thread>();
     
    // setup threads
    foreach (string strFile in strFiles)
    {
      string file = strFile;
      var thread = new Thread(() =>
      {
    	Log.Info("PulginManager: Start loading plugin '{0}'", file);
    	LoadPlugin(file);
    	Log.Info("PulginManager: Finished loading plugin '{0}'", file);
      });
      workerThreads.Add(thread);
    }
     
    // start all threads
    foreach (Thread thread in workerThreads)
    {
      thread.Start();
    }
     
    // wait for all threads to finish
    foreach (Thread thread in workerThreads)
    {
      thread.Join();
    }
     
    Last edited:

    offbyone

    Development Group
  • Team MediaPortal
  • April 26, 2008
    3,989
    3,712
    Stuttgart
    Home Country
    Germany Germany
    If I imagine a bigger MP install with 50 plugins, starting 50 thread at once to init them is probably not a good idea. Generally, when I do multi-threading, I try not to spawn more threads than CPU cores (anything else is more overhead anyway).
    To be honest the problem is coming from badly implemented plugins anyway. If a plugin does time consuming things in the Init phase it should spawn a thread for those itself. Your change will run the init code of all plugins at once, which IMHO imposes an even bigger risk at either breaking or deadlocking MP.
    There are so many "not-threadsafe" static objects in the MP core that are accessed by most plugins. Currently they do that one by one. Doing this simultaneously would actually require some good locking mechanisms. If you are a plugin dev it is highly possible that your multi-threading knowledge is close to zero.
    I also think it is most likely one plugin that blocks startup by a lot of time and the approach won't really fix that, because we still need to wait for the "slowest" plugin to finish.
    While I think it is a good idea to speed up the startup time, before investing too much - test this with lots of community plugins first to see what can happen.
     

    Scythe42

    Retired Team Member
  • Premium Supporter
  • June 20, 2009
    2,065
    2,703
    51
    Berlin
    Home Country
    Germany Germany
    Limiting running threads can of course be done easily. Thanks for pointing that one out. Sure, perfect way would be one "software thread" by "hardware thread". But they don't run that long. I'd prefer maxed out CPU and IO for a few seconds during startup instead of 30 seconds and a nearly idle system. What's there should be used. But I see that as fine tuning to not overload the system.

    First it must be found out if this actually works. If it doesn't it's a no go and at least I learned how to do threads in C#....

    And that's why I asked for Threads vs. ThreadPool unused. The later seems to be less expensive, even though it might not be as effective.

    Any suggestions?
     
    Last edited:

    tourettes

    Retired Team Member
  • Premium Supporter
  • January 7, 2005
    17,301
    4,800
    If I imagine a bigger MP install with 50 plugins, starting 50 thread at once to init them is probably not a good idea. Generally, when I do multi-threading, I try not to spawn more threads than CPU cores (anything else is more overhead anyway).

    Actually spawning more threads than there are CPU cores is sometimes wise - if you know that the threads are going to be blocked by some external event (I/O, network etc.) then you can yield better results with > CPU core number of threads.

    There are so many "not-threadsafe" static objects in the MP core that are accessed by most plugins. Currently they do that one by one. Doing this simultaneously would actually require some good locking mechanisms. If you are a plugin dev it is highly possible that your multi-threading knowledge is close to zero.

    Yep, it sounds extremely error prone area to tweak. But we will see after there is a test build available :p
     

    Scythe42

    Retired Team Member
  • Premium Supporter
  • June 20, 2009
    2,065
    2,703
    51
    Berlin
    Home Country
    Germany Germany
    Makes sense, I'm not touching anything how the actual plugins run or what is inside the DLL. Not leaving the Code of PluginManger.cs here.

    The ThreadPool seems to be the right choice after all. I found some nice MS Blog Entry explaining some background what the OS does differently and when to NOT to use ThreadPools.

    Need to check how a started QueueUserWorkItem indicates that the worker threads has finished as there is no Join to check if the thread is already finished. Probably there's some example on MSDN before I do a dirty hack here. WaitHandle.WaitAll in combination with ManualResetEvent seems to be the common practice.

    Anyway, we'll see how this turns out and apply it to to all plugin loading methods and install a bunch of plugins on my dev VM. And then watch MP crash :)
     
    Last edited:

    Oxan

    Retired Team Member
  • Premium Supporter
  • August 29, 2009
    1,730
    1,124
    Home Country
    Netherlands Netherlands
    You might want to look into the Task Parallel Library (TPL) as well, it has an even better interface than ThreadPool and uses the same backend. And it has a Join-like method. ;) Seems to be .NET4+ though, don't know if that is a problem for MP.

    Also, make sure to catch all exceptions thrown in spawned threads (for TPL, ThreadPool and manually started threads), these will crash MP without anything in the logs otherwise.
     
    Last edited by a moderator:

    Scythe42

    Retired Team Member
  • Premium Supporter
  • June 20, 2009
    2,065
    2,703
    51
    Berlin
    Home Country
    Germany Germany
    And here we go with Pool Threads:

    Code:
    2013-01-27 22:29:45.493164 [Debug][MPMain(1)]: PluginManager: Loading plugins with up to 500 threads from a maximum of 500
    2013-01-27 22:29:45.495117 [Debug][(6)]: PluginManager: Plugin loading dalyed by 0.9766 ms
    2013-01-27 22:29:45.495117 [Info.][(6)]: PluginManager: Start loading plugin '\process\PowerSchedulerClientPlugin.dll'
    2013-01-27 22:29:45.495117 [Debug][(9)]: PluginManager: Plugin loading dalyed by 0.9766 ms
    2013-01-27 22:29:45.495117 [Info.][(9)]: PluginManager: Start loading plugin '\process\ProcessPlugins.dll'
    2013-01-27 22:29:45.498046 [Info.][(6)]: PluginManager: Finished loading plugin '\process\PowerSchedulerClientPlugin.dll' in 2.9296 ms
    2013-01-27 22:29:45.498046 [Info.][(9)]: Load plugins from: C:\Program Files (x86)\Team MediaPortal\MediaPortal\Plugins\process\ProcessPlugins.dll
    2013-01-27 22:29:45.500976 [Info.][(9)]: File Version: 1.2.200.0
    2013-01-27 22:29:45.511718 [Info.][(9)]: PluginManager: Finished loading plugin '\process\ProcessPlugins.dll' in 16.6015 ms
    So much for the number of threads.

    Windows itself tells me that there are 500 worker threads in the pool available from a maximum of 500, meaning no worker threads are currenttly running. System is more or less idle. And according to the MSDN docs I fulfill the requirements for them. They list when not to use them and use dedicated threads instead.

    Windows adjust these numbers probably when needed. It decides what is good for the system not we. Numbers are from a Core i7 doing nothing beside starting MP on a VM that got assigned one core.

    Note that the roughly 1ms delay comes from Windows. It check periodically for new worker threads to execute from the available pool.

    And here the code:
    Code:
    int pluginsToLoad = strFiles.Length;
    using(var resetEvent = new ManualResetEvent(false))
    {
      // initialize state list
      var states = new List<int>();
      for (int i = 0; i < strFiles.Length; i++)
      {
    	states.Add(i);
      }
     
      // get information about available pool threads
      int maxThreads;
      int availableThreads;
      int portThreads;
      ThreadPool.GetMaxThreads(out maxThreads, out portThreads);
      ThreadPool.GetAvailableThreads(out availableThreads, out portThreads);
      Log.Debug("PluginManager: Loading plugins with up to {0} threads from a maximum of {1}", availableThreads, maxThreads);
     
      // load all plugins using available worker threads
      for (int i = 0; i < strFiles.Length; i++)
      {
    	string file = strFiles[i];
    	DateTime queueTime = DateTime.Now;
    	ThreadPool.QueueUserWorkItem(
    	  x =>
    		{
    		  // get relative plugin file name
    		  string removeString = Config.GetFolder(Config.Dir.Plugins);
    		  int index = file.IndexOf(removeString, StringComparison.Ordinal);
    		  string pluginFile = (index < 0) ? file : file.Remove(index, removeString.Length);
     
    		  DateTime startTime = DateTime.Now;
    		  TimeSpan delay = startTime - queueTime;
    		  Log.Debug("PluginManager: Plugin loading dalyed by {0} ms", delay.TotalMilliseconds);
    		  Log.Info("PluginManager: Start loading plugin '{0}'", pluginFile);
     
    		  LoadPlugin(file);
     
    		  DateTime endTime = DateTime.Now;
    		  TimeSpan runningTime = endTime - startTime;
    		  Log.Info("PluginManager: Finished loading plugin '{0}' in {1} ms", pluginFile, runningTime.TotalMilliseconds);
     
    		  // safely decrement the counter
    		  if (Interlocked.Decrement(ref pluginsToLoad) == 0)
    		  {
    			// ReSharper disable AccessToDisposedClosure
    			resetEvent.Set();
    			// ReSharper restore AccessToDisposedClosure
    		  }
    	  }, states[i]);
      }
     
      // wait until all worker threads are finished
      resetEvent.WaitOne();
    }

    Logging takes up most of the space at the moment. Will refactor later and add some more stuff like exception handling.

    Resharper complains about the resetEvent.Set() and thinks it is access to disposed closure. But IMHO that is clearly not. If I am wrong here, please let me know.

    I guess it doesn't understand the Interlocked Decrement. Why I am using it with a ref instead of just doing a pluginsToLoad--? Because if two threads would do it at the same time (very unlikely to ever happen though), we end up in deadlock as the threads would never finish and block each other at that point.

    I do this because WaitAll will not wait for more than 64 pool threads to be finished. Not that we have that much, but still I don't want to work in 64 chunks here or workaround that issue with some more complex object definitions. Makes the code unnecessary complex.

    Moving on from Load() to LoadWindowPlugins() and then Start()/Stop() and that's all that can be done PluginManager.cs. Nice clean usage of available worker threads. And as the actual plugin loading happens over Assembly.LoadForm I don't see any not thread safe issues so far. Doesn't mean there aren't any.

    Still waiting for a crash, but for that I need to add a ton of plugins....

    The PluginManager.cs code is straight forward and simple. Most stuff is the compatibility handling. Hard to read because everything here is designed around try/catch and ifs with continue statements. Nesting goes therefore a bit more deeper. But that's the nature of the logic.
     
    Last edited:

    Scythe42

    Retired Team Member
  • Premium Supporter
  • June 20, 2009
    2,065
    2,703
    51
    Berlin
    Home Country
    Germany Germany
    You might want to look into the Task Parallel Library (TPL) as well, it has an even better interface than ThreadPool and uses the same backend. And it has a Join-like method. ;) Seems to be .NET4+ though, don't know if that is a problem for MP.

    Also, make sure to catch all exceptions thrown in spawned threads (for TPL, ThreadPool and manually started threads), these will crash MP without anything in the logs otherwise.
    .NET4+ seems to be a problem at the moment. Didn't find some other stuff that is only available in 4+. But will read up on TPL for sure. Thanks for the tip.

    Looks like I have to stay with ThreadPool for now.

    Will add exception handling to some parts of PluginManager.cs later. Sure some nasty code outside it can crash MP. I thought to disable threaded loading/starting for Debug compiles though.

    But that's already fine tuning if everything else should work out.

    Edit:
    Oh I see PLINQ! As as like LINQ a lot that's definitely something for me!
     
    Last edited:

    l337

    MP Donator
  • Premium Supporter
  • December 18, 2012
    238
    73
    Home Country
    Germany Germany
    hier mein system

    System Type: Singleseat with TV-Server
    MP-Version: 1.3 Beta
    MediaPortal Skin: Legnod MOD TITAN Custom
    Windows Version: win 8 x64
    CPU: Intel core i 5
    Mainboard: ASROCK z68
    RAM: 4 gb
    HDD: SSD
    GFX-Card: Amd HD 7850
    1. TV Card: Digital Device v5
    2. TV-Card: Digital Device v5
    HTPC Case: Antec Fusion
    Video Codec: h.264: Lav / Arcsoft
    Audio Codec: ac3: Lav
    Display: Samsung 60 Zoll Ps60e6500
    Display Connection: HDMI
    TV Provider:
    Astra 19.2
     
    Last edited:
    Status
    Not open for further replies.

    Users who are viewing this thread

    Top Bottom