handmade.network » Forums » Single Instance Program with a Twist
Atomic
Adam Byrd
3 posts

None

#11005 Single Instance Program with a Twist
3 weeks, 5 days ago Edited by Adam Byrd on Feb. 24, 2017, 3:34 a.m.

I'm looking for a bit of Win32 help here.

I have a small program that runs in the background, listens for key presses, and automates stuff I don't feel like doing (mostly changing settings and showing notifications). It's mostly just an excuse to practice C++ again and learn more about the Win32 API.

What I want to do is enforce a single instance policy for the program. The twist is I want the surviving process to be the most recently opened process, not the existing process. This is complicated by the fact that hotkeys can only be registered for once in windows, making this a 'shared resource'.

Now, I've implemented this in a naive way with an event. It works like this:

    Process 1
  • Starts, creates the event.
  • Event didn't already exist, this is the first process.
  • RegisterHotkey
  • In the message loop wait for input or the event.

  • Process 2
  • Starts, creates the event.
  • Event already exists, this is not the first process.
  • Signal the event.
  • Wait on the event.

  • Process 1
  • Wakes up due to the event.
  • UnregisterHotkey
  • Signal the event.
  • Exit

  • Process 2
  • Wakes up due to the event.
  • RegisterHotkey
  • In the message loop wait for input or the event.

This mostly works, but it can fail in a couple places. One, since signalling an already signaled event has no effect, information is lost when two threads signal the event before it gets used. I don't think I managed to get this to happen in practice, but in theory it would lead to one of the threads getting stuck on a wait function. Two, the order in which threads are woken by the event is not strictly FIFO. It's mostly FIFO, hence the pattern mostly working. This manifests in a few ways, the important one being when a new thread signals the event it may wake a thread that isn't the one that actually owns the hotkeys and that usually leads to no one owning the hotkeys and everyone shutting down, including the newest instance. I'm able to cause the second issue fairly easily by highlighting the exe, holding enter for a few seconds, and waiting for Windows to decide to start waking threads 'out of order'.

I'm looking for suggestions on how to handle this robustly.

I've come up with a couple ideas that I'm fairly certain would work, but they're always complicated, involving 3 forms of synchronization (e.g. mutex for the hotkeys, manual reset event to signal threads to shutdown, and shared memory to know who the newest process is). I'm assuming there's simpler, more clever ways to handle this I've yet to come up with.

Here are my requirements:
  • The newest process always wins.
  • Solution uses 3 or fewer forms of synchronization.
  • OPTIONAL: An older process is notified once for every new process.
(remember, this is an exercise for fun and learning so I don't care that my requirements don't map to some 'real world use case').

The code is here if anyone wants to poke around: https://github.com/akbyrd/Pigeon

The optional requirement is because I show a notification when a process is superseded and it's fun to watch this happen: http://i.imgur.com/ApVYTLd.gif

None
mmozeiko
Mārtiņš Možeiko
1239 posts
1 project
#11006 Single Instance Program with a Twist
3 weeks, 5 days ago

Instead of events you could use windows message. SendMessage(HWND_BROADCAST, ...) will end your specific message to all other windows and there's no risk to lose the event.

To solve out-of-order problem, you could get process handle and check when it was started (GetProcessTimes). If it is newer than your start time that means you are not latest process and you need to exit.
Atomic
Adam Byrd
3 posts

None

#11007 Single Instance Program with a Twist
3 weeks, 5 days ago

Hmm, I thought about SendMessage briefly, but I had forgotten about HWND_BROADCAST. I could broadcast and include the startup time as one of the parameters. I'm a bit hesitant because I'd rather not send a message to every window in existence if I can avoid it. I'll have to look into that. Do you know of a way to limit the broadcast to a specific window class or something?

I shied away from getting process handles for the same reason: I don't want to enumerate every running process.

None
mmozeiko
Mārtiņš Možeiko
1239 posts
1 project
#11008 Single Instance Program with a Twist
3 weeks, 5 days ago

I don't think you can limit message broadcasting to subset of windows.
But yeah, instead of enumerating processes you either send startup time or your process id and let the receiver figure out time.