From 4a0411b7fc6bcd156210542cb148a62716aae196 Mon Sep 17 00:00:00 2001 From: Yeechan Lu Date: Tue, 25 Mar 2014 17:08:35 +0800 Subject: [PATCH] Fix crash on querying when plugins are initializing --- Wox/Commands/SystemCommand.cs | 9 +-- Wox/Helper/Forker.cs | 134 ++++++++++++++++++++++++++++++++++ Wox/PluginLoader/Plugins.cs | 24 +++++- Wox/Wox.csproj | 1 + 4 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 Wox/Helper/Forker.cs diff --git a/Wox/Commands/SystemCommand.cs b/Wox/Commands/SystemCommand.cs index f563c80969..e9ae34a85c 100644 --- a/Wox/Commands/SystemCommand.cs +++ b/Wox/Commands/SystemCommand.cs @@ -10,16 +10,9 @@ namespace Wox.Commands { public class SystemCommand : BaseCommand { - private List systemPlugins; - - public SystemCommand() - { - systemPlugins = Plugins.AllPlugins.Where(o => o.Metadata.PluginType == PluginType.System).ToList(); - } - public override void Dispatch(Query query) { - foreach (PluginPair pair in systemPlugins) + foreach (PluginPair pair in Plugins.AllPlugins.Where(o => o.Metadata.PluginType == PluginType.System)) { PluginPair pair1 = pair; ThreadPool.QueueUserWorkItem(state => diff --git a/Wox/Helper/Forker.cs b/Wox/Helper/Forker.cs new file mode 100644 index 0000000000..ab7b284715 --- /dev/null +++ b/Wox/Helper/Forker.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Wox.Helper +{ + /// + /// Provides a caller-friendly wrapper around parallel actions. + /// http://stackoverflow.com/a/540380 + /// + public sealed class Forker + { + int running; + private readonly object joinLock = new object(), eventLock = new object(); + + /// Raised when all operations have completed. + public event EventHandler AllComplete + { + add { lock (eventLock) { allComplete += value; } } + remove { lock (eventLock) { allComplete -= value; } } + } + private EventHandler allComplete; + /// Raised when each operation completes. + public event EventHandler ItemComplete + { + add { lock (eventLock) { itemComplete += value; } } + remove { lock (eventLock) { itemComplete -= value; } } + } + private EventHandler itemComplete; + + private void OnItemComplete(object state, Exception exception) + { + EventHandler itemHandler = itemComplete; // don't need to lock + if (itemHandler != null) itemHandler(this, new ParallelEventArgs(state, exception)); + if (Interlocked.Decrement(ref running) == 0) + { + EventHandler allHandler = allComplete; // don't need to lock + if (allHandler != null) allHandler(this, EventArgs.Empty); + lock (joinLock) + { + Monitor.PulseAll(joinLock); + } + } + } + + /// Adds a callback to invoke when each operation completes. + /// Current instance (for fluent API). + public Forker OnItemComplete(EventHandler handler) + { + if (handler == null) throw new ArgumentNullException("handler"); + ItemComplete += handler; + return this; + } + + /// Adds a callback to invoke when all operations are complete. + /// Current instance (for fluent API). + public Forker OnAllComplete(EventHandler handler) + { + if (handler == null) throw new ArgumentNullException("handler"); + AllComplete += handler; + return this; + } + + /// Waits for all operations to complete. + public void Join() + { + Join(-1); + } + + /// Waits (with timeout) for all operations to complete. + /// Whether all operations had completed before the timeout. + public bool Join(int millisecondsTimeout) + { + lock (joinLock) + { + if (CountRunning() == 0) return true; + Thread.SpinWait(1); // try our luck... + return (CountRunning() == 0) || + Monitor.Wait(joinLock, millisecondsTimeout); + } + } + + /// Indicates the number of incomplete operations. + /// The number of incomplete operations. + public int CountRunning() + { + return Interlocked.CompareExchange(ref running, 0, 0); + } + + /// Enqueues an operation. + /// The operation to perform. + /// The current instance (for fluent API). + public Forker Fork(ThreadStart action) { return Fork(action, null); } + + /// Enqueues an operation. + /// The operation to perform. + /// An opaque object, allowing the caller to identify operations. + /// The current instance (for fluent API). + public Forker Fork(ThreadStart action, object state) + { + if (action == null) throw new ArgumentNullException("action"); + Interlocked.Increment(ref running); + ThreadPool.QueueUserWorkItem(delegate + { + Exception exception = null; + try { action(); } + catch (Exception ex) { exception = ex; } + OnItemComplete(state, exception); + }); + return this; + } + + + /// Event arguments representing the completion of a parallel action. + public class ParallelEventArgs : EventArgs + { + private readonly object state; + private readonly Exception exception; + internal ParallelEventArgs(object state, Exception exception) + { + this.state = state; + this.exception = exception; + } + + /// The opaque state object that identifies the action (null otherwise). + public object State { get { return state; } } + + /// The exception thrown by the parallel action, or null if it completed without exception. + public Exception Exception { get { return exception; } } + } + } +} diff --git a/Wox/PluginLoader/Plugins.cs b/Wox/PluginLoader/Plugins.cs index 787c41641e..9d2b8b2d5e 100644 --- a/Wox/PluginLoader/Plugins.cs +++ b/Wox/PluginLoader/Plugins.cs @@ -16,9 +16,13 @@ namespace Wox.PluginLoader { private static string debuggerMode = null; private static List plugins = new List(); + private static ManualResetEvent initializing = null; public static void Init() { + if (initializing != null) return; + + initializing = new ManualResetEvent(false); plugins.Clear(); BasePluginLoader.ParsePluginsConfig(); @@ -28,6 +32,7 @@ namespace Wox.PluginLoader } plugins.AddRange(new CSharpPluginLoader().LoadPlugin()); + Forker forker = new Forker(); foreach (IPlugin plugin in plugins.Select(pluginPair => pluginPair.Plugin)) { IPlugin plugin1 = plugin; @@ -35,7 +40,7 @@ namespace Wox.PluginLoader if (pluginPair != null) { PluginMetadata metadata = pluginPair.Metadata; - ThreadPool.QueueUserWorkItem(o => plugin1.Init(new PluginInitContext() + forker.Fork(() => plugin1.Init(new PluginInitContext() { Plugins = plugins, CurrentPluginMetadata = metadata, @@ -58,11 +63,26 @@ namespace Wox.PluginLoader })); } } + + ThreadPool.QueueUserWorkItem(o => + { + forker.Join(); + initializing.Set(); + initializing = null; + }); } public static List AllPlugins { - get { return plugins; } + get + { + var init = initializing; + if (init != null) + { + init.WaitOne(); + } + return plugins; + } } public static bool HitThirdpartyKeyword(Query query) diff --git a/Wox/Wox.csproj b/Wox/Wox.csproj index ad221a4b7b..b2c732c22b 100644 --- a/Wox/Wox.csproj +++ b/Wox/Wox.csproj @@ -111,6 +111,7 @@ +