Code::Blocks Forums

User forums => General (but related to Code::Blocks) => Topic started by: thomas on September 07, 2005, 12:21:01 pm

Title: Multiple Instances
Post by: thomas on September 07, 2005, 12:21:01 pm
Code::Blocks used to have the multiple instances issue (you could accidentially open the same files in two instances and mess up terribly, especially when double-clicking something in Explorer). This behaviour has been adressed in RC1.

However, it is still not practical. Either you get the same behaviour as before (several copies) or you get a messagebox which disrupts your workflow, but you do not get the action you asked for.

So, during the last weeks, I have sporadically been thinking about this problem. Unluckily, it is not as easy as "well, tell the other instance..." because there is no such thing, at least not in an easy way, and not cross-platform.

wxWidgets does have some IPC support, but honestly, I am unable to understand both the API and the documentation that comes with it. It only seems to work on Windows if you have DDE running, too (which happens to be disabled on my machine).  Also, from various sources on the web, I gather it does not work the same everywhere. In one word: it kind of sucks.
There are proposed solutions of using named pipes or TCP sockets and whatever complicated stuff instead. Named pipes.... uh huh, who wants to implement that on several platforms? Step forward please.
TCP connections... great plan, but on my machine, like on many others too, ZoneAlarm pops up an alert each time a new application opens a TCP connection. This may lead to a quite negative first impression ("What the hell are they doing? Network == Spyware") on a new user.

So... in the end, I came up with the idea that using signal handlers and OS messages (with two separate codepaths) is maybe not so complicated and not so bad at all. It is only a rough idea yet, but well, look at it and give your ideas/comments :)

(I) Change MainFrame::MainFrame to pass CODEBLOCKS_WINDOW_ID to wxFrame rather than -1

(II) If another instance is found:
1. Create a temporary file inside WELL_KNOWN_FOLDER

2. Write the commandline into that file

3. a) on every OS except Windows:
    - send SIGHUP to the other process
    - SIGHUP is caught by the other process and ReadAlienCommandline() is called

3. b) on Windows:
 - use FindWindow(CODEBLOCKS_WINDOW_ID, -1) and PostMessage()
   to post SOME_MESSAGE to the first code::blocks window found
 - override virtual wxApp::ProcessMessage(), and call ReadAlienCommandline() if SOME_MESSAGE arrives

4. ReadAlienCommandline()
 - opens *any* present file in WELL_KNOWN_FOLDER
 - reads them in, parses the commandline, and does something in response (open a source file etc.)
 - deletes the tempfiles

Since we don't open a dozen of instances per second (one every couple of minutes is more like it), the overhead of writing and reading to/from a file should be nil.
Title: Re: Multiple Instances
Post by: grv575 on September 08, 2005, 11:31:45 pm

(II) If another instance is found:
1. Create a temporary file inside WELL_KNOWN_FOLDER

...

4. ReadAlienCommandline()
 - opens *any* present file in WELL_KNOWN_FOLDER
 - reads them in, parses the commandline, and does something in response (open a source file etc.)
 - deletes the tempfiles

Never, ever do that.   If the application crashes and leaves temp files behind, it is an absolute nightmare to deal with.  I had to maintain a socket application that did exactly this (temp files in the hopes of a custom cross-platform locking mechanism)  and it was insane to debug and get all states to work correctly.   Ended up just writing a simple app from scratch which used atomic IPC.  Much better to use the OS locking mechanisms or maybe look into the wxWidgets wrappers.
Title: Re: Multiple Instances
Post by: rickg22 on September 09, 2005, 12:01:50 am
I thought there was the appsomethingserver class so processes could communicate with each other... but shouldn't it be easy just not save the config/workspace if there was another instance running?
Title: Re: Multiple Instances
Post by: grv575 on September 09, 2005, 05:54:52 am
Well most multiinstance programs just ignore the issue and let each one modify configuration settings, etc.  Look at netscape.  If you have many netscape processes, they can each change settings and when they hit apply or ok, the settings are updated to the last one to hit apply or ok.

Windows explorer does this as well (you can save windows position, size when it is closed.  the last window closed for a specific folder is the one that gets the final write.  so it's position and size are the one that are restored when you open exporer again to that folder).

Edit: I don't quite get it though.  I have 2 instances of CB open.  I double click a .cbp file (with DDE server off) and it launches a new, 3rd CB.  So what's the gotcha?
Title: Re: Multiple Instances
Post by: rickg22 on September 09, 2005, 07:09:42 am
Hmmm that's what DDE is for. So it won't open a new C::B instance each time.
Title: Re: Multiple Instances
Post by: David Perfors on September 09, 2005, 08:12:18 am
Hmmm that's what DDE is for. So it won't open a new C::B instance each time.
But isn't DDE windows only? if so how 'bout *nix?
Title: Re: Multiple Instances
Post by: mandrav on September 09, 2005, 09:20:53 am
Hmmm that's what DDE is for. So it won't open a new C::B instance each time.
But isn't DDE windows only? if so how 'bout *nix?

According to wx docs, uner other platforms it uses TCP/IP or named pipes. Haven't tried it though and IIRC the DDE code in C::B is #ifdef'd for windows only...
Title: Re: Multiple Instances
Post by: rickg22 on September 09, 2005, 04:57:27 pm
The DDE server class is a subclass of ... some more generic class :lol: . Anyway, the wxwidgets docs say a TCP server is effectively cross-platform.
Title: Re: Multiple Instances
Post by: thomas on September 09, 2005, 04:58:34 pm
Edit: I don't quite get it though.  I have 2 instances of CB open.  I double click a .cbp file (with DDE server off) and it launches a new, 3rd CB.  So what's the gotcha?
The gotcha is this:
You may not want to use 10 different editors. I use Code::Blocks not only as a full IDE, but also as a plain editor for about everything (except for HTML, because there is not browser preview).
So you have a file open inside Code::Blocks and do something else (look up something on the web, for example, or answer the phone). Maybe you completely forget about Code::Blocks for an entire hour or two.
So any time later, you double-click on this file you wish to modify. This is quite intuitive because the Explorer window containing the file is still open. However, Code::Blocks is minimised, which you forgot about.
So what happens is you see your file opened in Code::Blocks, you work on, and ten minutes later, you realize that you have been working on two copies, neither of which is consistent.
The same can happen if you add new files to a project or change the build settings.

The idea with tempfiles in KNOWN_LOCATION would have the advantage that it is simple. Not good, but simple. Every system can create temp files, no DDE, OLE, or whatever obscure Microsoft-proprietary technology needed to make it work on Windows (and sadly, wx seems to rely on DDE).
True enough, TCP sockets are a much better solution, but like I pointed out, these may give people running an application-layer firewall on Windows an unneeded bad feeling. Most people I know get quite nervous when they suddenly see "Do you want to allow XXX to act as a server?" or "Do you want to allow XXX to access the internet?".

The problem with leftover files is not as bad as you picture it, really. Adobe Photoshop regularly leaves behind tempfiles on the order of 90-130 megabytes, and few people ever notice. Who would complain about a file which is, say 30 bytes in size.

The only question is what to do with leftover files from the application's PoV.

Lets see what can happen:
1. application starts, no other instance is running
---> no temp file is created. app does rm KNOWN_FOLDER/* to clean up (just to  be sure)
---> everybody is happy
2. application starts, sees another instance, creates tempfile, signals other instance, exits
--->other instance opens all files that are newer than a threshold (say 1-2 seconds?), executes the requested
commands, and does rm KNOWN_FOLDER/*
---> everybody is happy
3. application starts, sees another instance, creates tempfile, but crashes
---> no one knows about the tempfile, but no one cares, either
---> next time the tempfile is deleted anyway
---> the user is angry because the app crashed, but nothing else happens
4. application starts, sees another instance, creates tempfile, signals and exits, but other instance crashes
---> no one knows about the tempfile, but no one cares, either
---> the user is angry because the app crashed, but nothing else happens

So this does not look so bad, does it? A tempfile older than a second or two can safely be ignored, as we only start looking at them when receiving a signal (and that really does not take a long time to propagate). And since we "own" KNOWN_FOLDER, we can delete whatever we find in it, too.
Title: Re: Multiple Instances
Post by: mandrav on September 09, 2005, 06:33:06 pm
Thomas, just to get this straight: you 're talking about non-windows version, right? Because under windows it doesn't launch a new instance for every file you double-click in explorer, instead it re-uses the open instance...
Title: Re: Multiple Instances
Post by: grv575 on September 09, 2005, 07:49:40 pm
The gotcha is this:
...
So what happens is you see your file opened in Code::Blocks, you work on, and ten minutes later, you realize that you have been working on two copies, neither of which is consistent.
The same can happen if you add new files to a project or change the build settings.

What about file change detection a lot of editors do?  This would, whenever the window is made active, check the timestamp on the active file real quickly (or maybe all open files) and then alert the user the file has changed on disk - would they like to reload it or not.  Very handy, I use this all the time in ultraedit since then you can just have a file open, run some command that changes the file, and then ue will just reload it for you (easier than browsing for the file again).  This should keep files which are multiply open in sync as well.

Quote
Lets see what can happen:
1. application starts, no other instance is running
---> no temp file is created. app does rm KNOWN_FOLDER/* to clean up (just to  be sure)
---> everybody is happy

Yes but that create/delete file stuff is not atomic (and if you try to make it so, then you need to use OS locking stuff anyway).  Crazy states that you did not anticipate or code for can make for some really strange behavior when you do this sort of stuff.  For example, say the user highlights two files in explorer that they wish to modify.  Two CB processes start and this type of IPC mechanism will more than likely fall over.
Title: Re: Multiple Instances
Post by: mandrav on September 09, 2005, 08:37:50 pm
What about file change detection a lot of editors do?  This would, whenever the window is made active, check the timestamp on the active file real quickly (or maybe all open files) and then alert the user the file has changed on disk - would they like to reload it or not.  Very handy, I use this all the time in ultraedit since then you can just have a file open, run some command that changes the file, and then ue will just reload it for you (easier than browsing for the file again).  This should keep files which are multiply open in sync as well.

Already supported.
Title: Re: Multiple Instances
Post by: thomas on September 11, 2005, 12:48:21 pm
Yiannis,
unluckily no... Windows does not use the running instance of Code::Blocks.
I don't know if there is a way that Windows will actually do that. There are such things on other platforms - on MacOS, if I remember well from the old days, you set a flag in the executable's resource fork, and it just works, but no idea about Windows. Googling on terms like "single instance windows" only gets you variants of using a mutex and raising a window which has the same class as your class. That is not particularly useful, though (n.b.: Dev-CPP has the very same problem as Code::Blocks, but for example MS Word works just fine, so it must *somehow* be possible).

The current alert box "another instance is already running, aborting now" does prevent damage, but it disrupts your workflow, hence my thoughts about how to pass the commandline to another instance.

grv575,
indeed Code::Blocks does check file modification times. However, that does not necessarily help. If you have a modified copy in RAM and minimize the application, you can still open the same file in another instance and edit that. Especially if your thoughts leap a lot from one thing to another (like mine do, unluckily) and if you always have 20-30 files open in every program, this can go unnoticed quite easily. Eventually, you realize you have three files with two versions each in RAM, and you have to manually merge them with copy and paste, which is quite annoying and error-prone. Especially since you don't remember what you modified in which version of any of the three files...

To prevent that, you would have to kind of... exclusive lock the file or something, but that is even more evil than tempfiles. If your IDE crashes, you have to reboot Windows to edit your sources, ouch...
Or keep a "codeblocks open files log" or something, maybe. This is not as bad as exclusive locking.

EDIT:
Yes but that create/delete file stuff is not atomic (and if you try to make it so, then you need to use OS locking stuff anyway).  Crazy states that you did not anticipate or code for can make for some really strange behavior when you do this sort of stuff.  For example, say the user highlights two files in explorer that they wish to modify.  Two CB processes start and this type of IPC mechanism will more than likely fall over.
It should be possible to make it "atomic enough" without much pain.
All you have to do is three things:
So, if two instances are started, three things can happen:
For this to work, the order of actions must be different of course, but the above is for clarity  :)
Title: Re: Multiple Instances
Post by: mandrav on September 11, 2005, 03:34:17 pm
Yiannis,
unluckily no... Windows does not use the running instance of Code::Blocks.
I don't know if there is a way that Windows will actually do that. There are such things on other platforms - on MacOS, if I remember well from the old days, you set a flag in the executable's resource fork, and it just works, but no idea about Windows. Googling on terms like "single instance windows" only gets you variants of using a mutex and raising a window which has the same class as your class. That is not particularly useful, though (n.b.: Dev-CPP has the very same problem as Code::Blocks, but for example MS Word works just fine, so it must *somehow* be possible).
to work, the order of actions must be different of course, but the above is for clarity  :)

I don't use "single instance" C::B because I have to debug it :P
This may have something to do with it. If you allow multiple instances, at least in windows, it does re-use existing C::B instances for source files and projects. Only workspaces require a new instance to be launched - and this is intentional.
Maybe it only has to do with the single instance thing. Can you confirm this, because I haven't booted windows for a couple of weeks :P
Title: Re: Multiple Instances
Post by: thomas on September 11, 2005, 05:29:09 pm
Confirmed. The following image shows:
1) instance created by double-clicking on app.cpp
2) another instance created by double-clicking on app.cpp again
3) instance created by clicking on taskbar icon, opening my default workspace
4) instance created by clicking on taskbar icon again, also opening my default workspace

It is possible to edit app.cpp in either editor concurrently (as shown) and it is possible to change projects and workspaces in either instance.
The in my opinion correct behaviour would be to put the instance having app.cpp already open to the foreground instead of spawning more instances.

(http://img314.imageshack.us/img314/8362/multi8tc.png)
Title: Re: Multiple Instances
Post by: grv575 on September 11, 2005, 08:25:21 pm
Using version 1.1, the
Environment->Settings->Run DDE Server
works as expected.

After checking this option, and restarting codeblocks, I can click on .cpp files and they open in codeblocks, and do not open a new instance but always use the same one.  If I click on the same file in explorer more than once, then a file is only opened once.  So no duplicate files problem when the DDE server is running.

Try Explorer->Tools->Folder Options->File Types
delete or change current .cpp file associations, then right-click on a .cpp file in explorer->open with...->codeblocks->always use this program.  Should work as above if it was an association problem.

Thomas: as an atomic operation I mean that for a set of instructions, all instructions finish as if they were one instruction, so that the scheduler cannot interrupt the running process and schedule another process which executes the same block of instructions concurrently (which would lead to problems like race conditions -- i.e. say one process issues a createtempfile operation followed by a event notification "I have created my tempfile" to 2nd process.  Now these two instructions are not atomic.  You can have p1: create tempfile, context switch p2: create tempfile, before any event notifications occur.  This can lead to difficult bugs - race conditions are nothing to shake a stick at.  And to get around this, of course you would put a lock(mutex) {} block around the two instructions (createtempfile, sendevent) to make them "atomic" w.r.t. other processes.  However, this relies on OS locking mechanisms and is in general non-portable.  wxWidgets wrappers must provide some cross-platform equivalents that do use the OS locking mechanisms specific to each platform, so would probably be ideal (haven't looked at them yet though)).
Title: Re: Multiple Instances
Post by: thomas on September 12, 2005, 01:35:52 am
Using version 1.1, the
Environment->Settings->Run DDE Server
works as expected.
Hmm doesnt work for me on RC1-1 :(
Ah well... will have to live with it then.

Thomas: as an atomic operation I mean [...] scheduler cannot interrupt the running process [...]
i.e. say one process issues a createtempfile operation followed by a event notification "I have created my tempfile" to 2nd process.  Now these two instructions are not atomic.  You can have p1: create tempfile, context switch p2: create tempfile, before any event notifications occur.
Yes, I understand "atomic" :)
This is not really a problem, though. The case you describe is pretty much the same as case 3 in my above post. If you are really pedantic about what you execute and what you delete, and when, there are not so many bad things that can happen.
- When creating a tempfile, you can open it write-exclusive, this does not cost you anything. If the app crashes, you have one stale file lingering, but so what... after next reboot, it will be unlocked and can be deleted. As long as you have not closed your file, no one can delete it. So if another instance gets a notification from anywhere, and tries to delete that file, it will know that its contents is not valid.
- When getting a notification, you just look at everything you can get from that one directory.
If it is old, forget it and delete it. That way, no stale files persist forever.
Go on, read it in and try to delete the file. If the delete fails, forget it - it is still in use and you'll probably get another notification soon.
Otherwise, handle the contents that you have read in.

This is truly not atomic, but it is "atomic enough", that is, atomic where it absolutely must. When opening two files simultaneously, it does not matter in which order they are opened or how many milliseconds lie between opening them. Atomicity is only important here insofar as no requests should be completely lost, and no garbled data should be used. This is guaranteed by write-exclusive and delete. The good thing is it uses no OS-dependent features. The worst thing to happen is scanning a directory and opening one or two files in vain.
But well, given that the DDE server seems to work, this is purely academic now. Never change a working system. :)

EDIT: Little mistake of mine. You might rather append a char to the file before reading its contents. This is better because the read-delete strategy is in fact flawed. Appending a char to the file ensures that it has been closed by the other process (thus, its data is valid), and you can still read it in (minus one char) afterwards. This really makes it atomic. The delete can be any time later then.
Title: Re: Multiple Instances
Post by: grv575 on September 12, 2005, 02:13:01 am
Hmm doesnt work for me on RC1-1 :(
Ah well... will have to live with it then.

Yeah I meant RC1-1.  For me whichever codeblocks was opened last (or a new one only if there are no CB's open) gets the files clicked in explorer.  I guess it obtaines the DDE server ownership.  I had .cpp files associated with dev-cpp but just changing the association to CB (so no special command strings for the file association) and enabling dde seemed to work.  Maybe clearing configuration settings as well?  I'm on WinXP.

Quote
EDIT: Little mistake of mine. You might rather append a char to the file before reading its contents. This is better because the read-delete strategy is in fact flawed. Appending a char to the file ensures that it has been closed by the other process (thus, its data is valid), and you can still read it in (minus one char) afterwards. This really makes it atomic. The delete can be any time later then.

See it's tricky  :)  I guess there is a way to do it right though (unix uses lock files after all which is similar)  If I did use some system like this for a project I think I'd write the smallest test app as a testbed and then run that through as many different conditions as possible to see that it behaved as expected (the previous sockets project I had to maintain was some guy's senior thesis which he just threw together and then ran off to microsoft... I guess I've grown skeptical of custom solutions (especially ones which don't come with lots of docs and unit test) rather than looking up on MSDN or whatever "how to do X").
Title: Re: Multiple Instances
Post by: zieQ on September 12, 2005, 09:08:38 am
Just a little side note about lock files. This should be implemented as an option which would be set by default. I don't want to reboot Windows each time C::B will crash while debugging/implementing a new feature ;)
Title: Re: Multiple Instances
Post by: thomas on September 15, 2005, 07:28:12 pm
And it gets better...

Today, I accidentially clicked on a .c file while Code::Blocks was running, and it worked just fine.

Funnily, it will work for .c, but not for any other file. To make sure it is not due to bad associations, I erased all source file associations using Explorer, this changed nothing. I then associated .xrc (previously unassigned) to Code::Blocks. It would open two instances, while .c would still reuse an existing one. Then I went to HKEY_CLASSES_ROOT, and checked the subkeys .c, .h, .cpp, and .cbp as well as their Codeblocks.* counterparts. They are precisely identical (except for the extension).
Next, I checked out HEAD and looked at the code that does the DDE handling. The function extracts the filename with a regex and calls MainFrame::Open it without ever looking at an extension.
How can it work for .c and not for .cpp? This is beyond me.
Title: Re: Multiple Instances
Post by: grv575 on September 15, 2005, 11:30:26 pm
OK first try to clear out all references to file types.

delete HLCR\*.h,*.c,*.cpp
delete HKLM\software\classes\*.h,*.c,*.cpp
then delete all stuff in folder options->file types that remain

Click on a .cpp or .c in explorer and open with codeblocks, allow it to set itself as the default for these types (I got a messagebox asking).

Now the way the DDE server works is this:  *THE LAST* codeblocks that was opened (if you are using multiple CB instances) get registered as the DDE server.  So you have to be consistent.  Either always use windows explorer to open files in CB and don't start another instance if you plan on opening the same already open files using explorer.

See the problem is that you can open a couple cpp or c files.  It launches a CB instance.  Then you launch another CB instance, working on something else.  Now this new instance get the DDE server and if you try to open again files that are open in the first instance using windows explorer, then they will open in the second instance as well (since it now has the DDE server).

A fix for this behavior, hopefully giving better results, would be to change the single instance code.  1) always use a mutex to check for existing running CB instances (even if they unchecked the checkbox to disable multiple instances).  2) If the user allows multiple instances in the settings, then allow another CB to start _BUT_ do not startup the DDE plugin again.  Let the first CB instance always get be in charge of opening files from explorer.  Then you won't run into the problems above.
Title: Re: Multiple Instances
Post by: thomas on September 16, 2005, 04:10:35 pm
delete HLCR\*.h,*.c,*.cpp
delete HKLM\software\classes\*.h,*.c,*.cpp
then delete all stuff in folder options->file types that remain

Click on a .cpp or .c in explorer and open with codeblocks, allow it to set itself as the default for these types (I got a messagebox asking).
Did all that with the same result again.
Title: Re: Multiple Instances
Post by: thomas on September 16, 2005, 09:23:53 pm
What about using ptypes (http://www.melikyan.com/ptypes/index.html)?

It is zblib licensed and offers cross-platform named pipes: http://www.melikyan.com/ptypes/doc/streams.namedpipe.html

A named pipe should work just fine, and it needs no obscure server processes. :)