Proper Single Instance Implementation

Reworked single instance code to better handle being launched with
context menu.

Previously:
- On launch, process would try to open an `EventWaitHandle`. If it
didn't exist, then process assumed it was first and created it
- Multiple processes launching at same time would all see the
`EventWaitHandle` did not exist before any process could create it
- This led to multiple instances of program running

Now a mutex is used:
- Processes will all try to open the mutex. Only first one will succeed
- Process that opens the mutex will setup the `EventWaitHandle`
- Other processes won't get the mutex and will be directed to use the
`EventWaitHandle` to pass their command line args to the running
instance
This commit is contained in:
Brian 2016-03-10 16:29:01 -05:00
parent 538acf30e8
commit 29e8e739e3
3 changed files with 58 additions and 52 deletions

View file

@ -35,56 +35,45 @@ namespace SingleInstanceApplication
public static class ApplicationInstanceManager
{
private static Semaphore semaphore;
private static string appName = "ShareX";
private static string eventName = string.Format("{0}-{1}", Environment.MachineName, appName);
private static string semaphoreName = string.Format("{0}{1}", eventName, "Semaphore");
[DebuggerStepThrough]
public static bool CreateSingleInstance(string name, EventHandler<InstanceCallbackEventArgs> callback, string[] args)
public static void CreateFirstInstance(EventHandler<InstanceCallbackEventArgs> callback)
{
string eventName = string.Format("{0}-{1}", Environment.MachineName, name);
string semaphoreName = string.Format("{0}{1}", eventName, "Semaphore");
bool createdNew;
InstanceProxy.IsFirstInstance = false;
InstanceProxy.CommandLineArgs = args;
try
using (EventWaitHandle eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName, out createdNew))
{
using (EventWaitHandle eventWaitHandle = EventWaitHandle.OpenExisting(eventName))
// Mixing single instance and multi instance (via command line parameter) copies of the program can
// result in CreateFirstInstance being called if it isn't really the first one. Make sure this is
// really first instance by detecting if EventWaitHandle was created
if (createdNew != true)
{
semaphore = Semaphore.OpenExisting(semaphoreName);
semaphore.WaitOne();
UpdateRemoteObject(name);
if (eventWaitHandle != null) eventWaitHandle.Set();
return;
}
Environment.Exit(0);
semaphore = new Semaphore(1, 1, semaphoreName);
ThreadPool.RegisterWaitForSingleObject(eventWaitHandle, WaitOrTimerCallback, callback, Timeout.Infinite, false);
RegisterRemoteType(appName);
}
catch
{
InstanceProxy.IsFirstInstance = true;
using (EventWaitHandle eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
{
semaphore = new Semaphore(1, 1, semaphoreName);
ThreadPool.RegisterWaitForSingleObject(eventWaitHandle, WaitOrTimerCallback, callback, Timeout.Infinite, false);
}
RegisterRemoteType(name);
}
return InstanceProxy.IsFirstInstance;
}
public static bool CreateSingleInstance(EventHandler<InstanceCallbackEventArgs> callback, string[] args)
public static void CreateMultipleInstance(EventHandler<InstanceCallbackEventArgs> callback, string[] args)
{
try
{
return CreateSingleInstance("ShareX", callback, args);
}
catch
InstanceProxy.CommandLineArgs = args;
using (EventWaitHandle eventWaitHandle = EventWaitHandle.OpenExisting(eventName))
{
semaphore = Semaphore.OpenExisting(semaphoreName);
semaphore.WaitOne();
UpdateRemoteObject(appName);
if (eventWaitHandle != null) eventWaitHandle.Set();
}
return true;
Environment.Exit(0);
}
private static void UpdateRemoteObject(string uri)
@ -96,7 +85,7 @@ private static void UpdateRemoteObject(string uri)
if (proxy != null)
{
proxy.SetCommandLineArgs(InstanceProxy.IsFirstInstance, InstanceProxy.CommandLineArgs);
proxy.SetCommandLineArgs(InstanceProxy.CommandLineArgs);
}
ChannelServices.UnregisterChannel(clientChannel);
@ -122,7 +111,7 @@ private static void WaitOrTimerCallback(object state, bool timedOut)
EventHandler<InstanceCallbackEventArgs> callback = state as EventHandler<InstanceCallbackEventArgs>;
if (callback == null) return;
callback(state, new InstanceCallbackEventArgs(InstanceProxy.IsFirstInstance, InstanceProxy.CommandLineArgs));
callback(state, new InstanceCallbackEventArgs(InstanceProxy.CommandLineArgs));
semaphore.Release();
}

View file

@ -32,27 +32,21 @@ namespace SingleInstanceApplication
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
internal class InstanceProxy : MarshalByRefObject
{
public static bool IsFirstInstance { get; internal set; }
public static string[] CommandLineArgs { get; internal set; }
public void SetCommandLineArgs(bool isFirstInstance, string[] commandLineArgs)
public void SetCommandLineArgs(string[] commandLineArgs)
{
IsFirstInstance = isFirstInstance;
CommandLineArgs = commandLineArgs;
}
}
public class InstanceCallbackEventArgs : EventArgs
{
internal InstanceCallbackEventArgs(bool isFirstInstance, string[] commandLineArgs)
internal InstanceCallbackEventArgs(string[] commandLineArgs)
{
IsFirstInstance = isFirstInstance;
CommandLineArgs = commandLineArgs;
}
public bool IsFirstInstance { get; private set; }
public string[] CommandLineArgs { get; private set; }
}
}

View file

@ -268,16 +268,39 @@ private static void Main(string[] args)
IsMultiInstance = CLI.IsCommandExist("multi", "m");
if (IsMultiInstance || ApplicationInstanceManager.CreateSingleInstance(SingleInstanceCallback, args))
// Ensuring only one instance of program based on http://stackoverflow.com/questions/229565/what-is-a-good-pattern-for-using-a-global-mutex-in-c
using (Mutex mutex = new Mutex(false, "82E6AC09-0FEF-4390-AD9F-0DD3F5561EFC")) // Specific mutex required for installer
{
using (Mutex mutex = new Mutex(false, "82E6AC09-0FEF-4390-AD9F-0DD3F5561EFC")) // Required for installer
bool hasHandle = false;
try
{
Run();
}
try
{
hasHandle = mutex.WaitOne(100, false);
if (hasHandle == false && !IsMultiInstance)
{
ApplicationInstanceManager.CreateMultipleInstance(SingleInstanceCallback, args);
}
}
catch (AbandonedMutexException)
{
// Log the mutex was abandoned in another process, it will still get acquired
DebugHelper.WriteLine("Single instance mutex found abandoned from another process");
hasHandle = true;
}
if (restarting)
ApplicationInstanceManager.CreateFirstInstance(SingleInstanceCallback);
Run();
if (restarting)
{
Process.Start(Application.ExecutablePath);
}
}
finally
{
Process.Start(Application.ExecutablePath);
if (hasHandle)
mutex.ReleaseMutex();
}
}
}