OpenURL Bug in Linux, now with sample code!

BlitzMax Forums/BlitzMax Bug Reports/OpenURL Bug in Linux, now with sample code!

I've been trying to track sample code down for weeks and I think I have something for you.

- Open the digesteroids sample.
- On line 1483 (after the Game.ShutDown() method) add OpenURL ("http://google.com")
- compile, run, then quit

On my machine (Ubuntu 8,04, BlitzMax 1.30) the browser successfully opens and goes to Google perhaps 1 out of 10 times; the remaining 9 times either nothing happens or Firefox itself hangs and the process needs to be killed before you can access the browser again. It seems to be slightly more successful if a browser is open in the background before you compile the program.

This is peculiar. Does running the compiled program by double-clicking yield consistent behaviour? I get the same as you when I hit "Build & Run", but it works really well when run from outside the IDE.

I don't know about the above example, but my game (which is of course significantly larger than Digesteroids) will exhibit this exact same bug running from the compiled binary. My first encounter with this bug came from users telling me when they clicked "Order Game" from the game's main menu (which calls EndGraphics, OpenURL("http://basiliskgames.com") and then End), the game hung, or Firefox hung, or simply nothing happened and the program just exits. I have confirmed this over and over, and you can certainly download the Linux demo yourself to see it happen but I can assure you it is identical to what happens in the Digesteroids sample.

Odd, I know. It really seems to be linked to program size as several other people have also noted.

Does it make a difference if you add a WAIT statement before the openURL?

For me it doesn't. I've tried numerous things such as Wait(), GCCollect(), or EndGraphics before and after I call OpenURL() and it doesn't help.

Ah, this is the one I mentioned before :)

The odd things is that if you had the OpenURL in a much shorter program, it works all the time - I suspect some memory is being corrupted somewhere.

Does this work, or does it have the same problems?

SuperStrict

Framework BRL.System
Import BRL.StandardIO


Print OpenURL2("http://blitzmax.com")



Function OpenURL2:Int( url:String)

?Not linux
	OpenURL(url)
?linux
Extern "c"
Function sys:Int( cmd:Byte Ptr ) ="system_"
End Extern

	Local s:Byte Ptr
	Local ret:Int
	
	If getenv_("KDE_FULL_DESKTOP")
		s = ("kfmclient exec ~q"+url+"~q").ToCString()
	ElseIf getenv_("GNOME_DESKTOP_SESSION_ID")
		s = ("gnome-open ~q"+url+"~q").ToCString()
	EndIf

	If s Then	
		ret = sys(s)
		MemFree(s)
	End If
	
	Return ret
?

End Function


@ Brucey:

I've done some similar tests myself, and experienced the same behaviour.

It doesn't matter whether you use system() (POSIX) or CreateProcess(). Stepping into the code by placing a DebugStop before fixes it for me, although it won't work from the IDE when run with Debug mode or Release mode. :-/

I've also tried the better command line utility (xdg-open) which I believe is supposed to be DE independent and is used by wxWidgets as its first port of call. I get the same results as described above. Running from the IDE (unless stepping through) doesn't work, but executing by double-clicking the executable in the file manager works fine.

Finally, running the commands "gnome-open" or "xdg-open" from the Terminal work fine for me, so that doesn't appear to be the problem. :-(

Brucey:

I tried your code out in my Book I project and it freezes just the same- and it doesn't matter if I run the game from the IDE or the binary, it just hangs the game when I try to open a browser.

I know this bug is no more important than any other, but I implore the BRL staff to find a fix as soon as possible. I need this to work for a special build of the game! :(

Tachyon:

Can you try the following example? This seems to work every time I run it on my Ubuntu PC.

OpenURL3("www.google.com")

Function OpenURL3(pUrl$)
	system_("xdg-open ~q" + pUrl$ + "~q")
	Delay 5000
	Input("")
EndFunction
If it still doesn't work, is there something particular about the URL you are using? Are you terminating your program (e.g. by calling End) straight after calling OpenURL()/OpenURL3(). Finally, are you in full-screen graphics mode when it is called, or windowed?

@SebHoll: That doesn't work either- game just hangs.

To answer your other questions:
1) I used your "www.google.com" URL this time, but in other attempts I have used "http://basiliskgames.com/purchase.html"
2) Yes, I call End immediately after OpenURL. I have also tried to add Delay 5000 and GCCollect() before and after along with the End. Results are the same.
3) My game operates in either full-screen or windowed. Results are the same either way.

Thanks for the quick response. Please don't give up! :)

Hmmm... This is strange...

Does it crash when you run the example code I posted "As is", i.e. as a separate brand new .bmx file?

That sample runs fine. In fact, just good old OpenURL works perfectly if the program is small.

That has been the problem from the start- trying to get a large enough sample program to cause OpenURL to freak out. A possible sample program could be the IDE itself, being as large as it is. I wonder if you added a small OpenURL somewhere in there triggered by a menu option if it would work...of course, if this OpenURL problem somehow exists only after creating a Graphics window, then the IDE would not exhibit the bug. Maybe worth a try for experimental purposes?

Tachyon, what happens when you add a & (ampersand) to the end of the command string of a custom call?

eg.
OpenURL3("www.google.com")

Function OpenURL3(pUrl$)
	system_("xdg-open ~q" + pUrl$ + "~q &")
	Delay 5000
	Input("")
EndFunction

..or..
s = ("gnome-open ~q"+url+"~q &").ToCString()

or similar... and note that it goes after the quote.

The ampersand causes the process to fork off and return immediately. In theory, whatever happened to the command at that point shouldn't affect your own app.

Nope. <sobs> This doesn't work either. The program locks hard (almost like stuck in an infinite loop, occupying 100% of one of my CPUs) and must be manually closed.

Brucey, you've got some pretty hefty BlitzMax/Linux programs somewhere in your repository, right? What happens when you throw OpenURL somewhere in one of them?

How much memory (i.e. RAM) is you're game using in between the GCCollect() call and OpenURL()?

@SebHoll: Brucey has performed some serious low-level investigation into this and provided me with a fix (hack). Seems to work well. I'll let him explain it all if he wants since it is his masterpiece, but for now my problem is fixed.

Thanks SebHoll, and super-duper thanks to Brucey! =D

Brucey has performed some serious low-level investigation into this and provided me with a fix (hack). Seems to work well. I'll let him explain it all if he wants since it is his masterpiece, but for now my problem is fixed.

That's not fair, Brucey! Keeping it to yourself like that... :-P So, go on - I'm eager to find out what the fix is as from my own investigations it looks as though this is actually a problem with fork()ing large processes. I managed to reproduce it in a short example, simply by allocating a large amount of memory (like 50MB) before calling OpenURL() and it crashed every time.

Spill the beans, you know you want to. ;-)

Here's the quick hack... Mark will probably want to implement it properly. (Mark, can't we just have a normal background thread running, rather than spewing out 1000 SIGALRMs every second, please? Both my VMs barf on it).

Anyhoo :-)

In BRL.mod/blitz.mod/blitz_app.h, add this line after the bbStartup(...
void bbset_time_interval(int ms);


In BRL.mod/blitz.mod/blitz.app.c, look for the function "static void set_time_interval..."
After it, add this little function :
void bbset_time_interval(int ms) {
  set_time_interval(ms);
}


In BRL.mod/system.mod/system.linux.bmx, at the bottom is the OpenURL method.
Before the If statement, add :
bbset_time_interval(0)

and, after the EndIf statement, add :
bbset_time_interval(1)


One last thing...

At the bottom of that file, you need to add an extern :
Extern
    Function bbset_time_interval(n:int)
End Extern


Remember, this is a Linux-only fix. If you add the code as described and then try to build this on anything other than Linux, it probably won't compile (you have been warned ;-)

If in doubt, back up first... or better yet, don't start hacking at the modules!!


:o)

Enjoy

So..... Let me get this straight. ;-) The reason BlitzMax is crashing when it is fork()ed is because of the timing code in BRL.System, whose Linux implementation involves emitting SIGALRM interrupts 1000 times a second. Your fix basically stops the timer and resumes it after the system() call.

I'm curious... How the hell did you manage to narrow it down to that interrupt? Surely there's a better way to do this kinda thing that with interrupts... You mention background threads... How would they work?

How the hell did you manage to narrow it down to that interrupt?

It took a while... and considering that gdb didn't help much, only indicating what was actually locked, not what was causing it :-p

You mention background threads... How would they work?

Dunno ;-)
But using signals seems kind of hard-core to me. I'd have thought you could just as well, run some kind of looping thread instead to catch the events, which doesn't employ signals. It seems that some external functions expect to be in control of signals when they kick off, and maybe get into a "race" with Blitz's low level implementation.

Don't ask me though. I ain't the expert.