MCGalaxy/GUI/Program.cs
UnknownShadow200 88c5c1dac3 Linux: Fix rare case where GUI would crash at startup (mostly when using standalone builds compiled with mkbundle)
This happened due to two factors:

1) the standalone build would show a message box warning because it couldn't change the current/working directory
2) the gui schedules InitServerTask to run on the background thread. This in turn will eventually call Server.Start on the background thread, which in turn will call Logger.Log(..) which in turn calls back to Window.LogMessage

Note that the UI can only be updated from the main/UI thread. Normally this wouldn't cause any issues, because LogMessage checked whether 'InvokeRequired' returned true or not - if it did, then LogMessage scheduled the message to be logged later on the main/UI thread by using BeginInvoke.

However, it was rarely possible that due to thread scheduling, the call to LogMessage from the background thread would be run *before* the main window control handle had finished being created.
And unexpectedly, 'InvokeRequired' would return *false* if the main window handle hadn't been created yet - and hence the background thread would attempt to directly update UI controls with catastrophic consequences. Sometimes this worked, but other times it would cause the app to SIGABRT and crash in X11 somewhere, usually in X11Keyboard:XCreateFontSet

So to workaround this I made the following changes:
1) failing to set working/current directory doesn't show a warning message box popup anymore
2) LogMessage doesn't check 'InvokeRequired' anymore and just always calls BeginInvoke to schedule the message logging on the main/UI thread

So in the most of the rare cases when this issue even happened, it shouldn't occur anymore now. However, the underlying issue still isn't completely solved.. if you change the code to show a message box before the main window, you'll still rarely end up with the main window being a black box with X11
2022-06-06 23:11:24 +10:00

92 lines
3.5 KiB
C#

/*
Copyright 2010 MCSharp team (Modified for use with MCZall/MCLawl/MCGalaxy)
Dual-licensed under the Educational Community License, Version 2.0 and
the GNU General Public License, Version 3 (the "Licenses"); you may
not use this file except in compliance with the Licenses. You may
obtain a copy of the Licenses at
http://www.opensource.org/licenses/ecl2.php
http://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing,
software distributed under the Licenses are distributed on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
namespace MCGalaxy.Gui
{
public static class Program
{
[STAThread]
public static void Main(string[] args) {
SetCurrentDirectory();
// separate method, in case MCGalaxy_.dll is missing
try {
StartGUI();
} catch (FileNotFoundException) {
// If MCGalaxy_.dll is missing, a FileNotFoundException will get thrown for MCGalaxy dll
Popup.Error("Cannot start server as MCGalaxy_.dll is missing from " + Environment.CurrentDirectory
+ "\n\nDownload it from " + Updater.UploadsURL);
return;
}
}
static void SetCurrentDirectory() {
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
try {
Environment.CurrentDirectory = path;
} catch {
// assembly.Location usually gives full path of the .exe, but has issues with mkbundle
// https://mono-devel-list.ximian.narkive.com/KfCAxY1F/mkbundle-assembly-getentryassembly
// https://stackoverflow.com/questions/57648241/reliably-get-location-of-bundled-executable-on-linux
// Rather than trying to guess when this issue happens, just don't bother at all
// (since most users will not be trying to run .exe from a different folder anyways)
}
}
static void StartGUI() {
FileLogger.Init();
Server.RestartPath = Application.ExecutablePath;
AppDomain.CurrentDomain.UnhandledException += GlobalExHandler;
Application.ThreadException += ThreadExHandler;
try {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault( false );
Application.Run(new Window());
} catch (Exception e) {
Logger.LogError(e);
}
}
static void LogAndRestart(Exception ex) {
Logger.LogError(ex);
FileLogger.Flush(null);
Thread.Sleep(500);
if (Server.Config.restartOnError) {
Thread stopThread = Server.Stop(true, "Server restart - unhandled error");
stopThread.Join();
}
}
static void GlobalExHandler(object sender, UnhandledExceptionEventArgs e) {
LogAndRestart((Exception)e.ExceptionObject);
}
static void ThreadExHandler(object sender, ThreadExceptionEventArgs e) {
LogAndRestart(e.Exception);
}
}
}