Problems:

1) The AutoRelog ChatBot stopped trying to log in after the 3rd attempt.
2) The AutoRelog ChatBot stopped trying to log in after the internet connection was interrupted. MCC froze.

2 problems have been solved.
This commit is contained in:
Somersault 2026-07-18 21:37:52 +03:00
parent 142fb107db
commit 9e271a2f06
8 changed files with 668 additions and 327 deletions

View file

@ -56,6 +56,13 @@ namespace MinecraftClient
private static bool useMcVersionOnce = false;
private static Thread? _restartThread = null;
private static readonly object _restartLock = new();
private static int _exitInProgress = 0;
private static int _restartInProgress = 0;
private static int _failureInProgress = 0;
private static int _restartAttemptGeneration = 0;
private static int _restartPending = 0;
private static int _pendingRestartDelaySeconds = 0;
private static bool _pendingRestartKeepAccountAndServerSettings = false;
private static string settingsIniPath = "MinecraftClient.ini";
// [SENTRY]
@ -914,22 +921,91 @@ namespace MinecraftClient
internal static bool TryRestart(int delaySeconds = 0, bool keepAccountAndServerSettings = false)
{
if (Interlocked.Exchange(ref _restartInProgress, 1) != 0)
{
lock (_restartLock)
{
Interlocked.Exchange(ref _restartPending, 1);
_pendingRestartDelaySeconds = delaySeconds;
_pendingRestartKeepAccountAndServerSettings = keepAccountAndServerSettings;
}
return true;
}
int attemptGeneration = Interlocked.Increment(ref _restartAttemptGeneration);
lock (_restartLock)
{
if (HasRestartPendingForAnotherThreadNoLock())
{
Interlocked.Exchange(ref _restartInProgress, 0);
Interlocked.Decrement(ref _restartAttemptGeneration);
return false;
}
try
{
if (ConsoleIO.Backend is not null)
ConsoleIO.Backend.OnInputChange -= ConsoleIO.OfflineAutocompleteHandler;
}
catch { }
try
{
ConsoleIO.Backend?.StopReadThread();
}
catch { }
try
{
ConsoleIO.CancelAutocomplete();
}
catch { }
try
{
ConsoleIO.Reset();
}
catch { }
ConsoleIO.Backend?.StopReadThread();
var thread = new Thread(new ThreadStart(delegate
{
try
{
if (client is not null) { client.Disconnect(); ConsoleIO.Reset(); }
McClient? activeClient = client;
if (activeClient is not null)
{
activeClient.Disconnect();
if (ReferenceEquals(client, activeClient))
client = null;
ConsoleIO.Reset();
}
try
{
ConsoleIO.CancelAutocomplete();
ConsoleIO.Reset();
}
catch { }
if (offlinePrompt is not null)
{
if (ConsoleIO.Backend is not null)
ConsoleIO.Backend.OnInputChange -= ConsoleIO.OfflineAutocompleteHandler;
offlinePrompt.Item2.Cancel(); offlinePrompt.Item1.Join(); offlinePrompt = null; ConsoleIO.Reset();
try
{
offlinePrompt.Item2.Cancel();
if (offlinePrompt.Item1.IsAlive)
{
if (!offlinePrompt.Item1.Join(TimeSpan.FromSeconds(1)))
offlinePrompt.Item1.Interrupt();
}
}
catch (Exception)
{
// Best-effort cleanup; the reconnect loop must continue.
}
finally
{
offlinePrompt = null;
ConsoleIO.Reset();
}
}
if (delaySeconds > 0)
{
@ -942,11 +1018,30 @@ namespace MinecraftClient
}
finally
{
Interlocked.Exchange(ref _restartInProgress, 0);
lock (_restartLock)
{
if (_restartThread == Thread.CurrentThread)
_restartThread = null;
}
if (Volatile.Read(ref _restartPending) != 0)
{
int nextDelaySeconds = 0;
bool nextKeepAccountAndServerSettings = false;
lock (_restartLock)
{
if (Volatile.Read(ref _restartPending) != 0)
{
nextDelaySeconds = _pendingRestartDelaySeconds;
nextKeepAccountAndServerSettings = _pendingRestartKeepAccountAndServerSettings;
Interlocked.Exchange(ref _restartPending, 0);
}
}
if (Volatile.Read(ref _restartPending) == 0)
TryRestart(nextDelaySeconds, nextKeepAccountAndServerSettings);
}
}
}));
_restartThread = thread;
@ -957,29 +1052,65 @@ namespace MinecraftClient
private static bool HasRestartPendingForAnotherThreadNoLock()
{
return _restartThread is not null
&& _restartThread.IsAlive
&& _restartThread != Thread.CurrentThread;
return Volatile.Read(ref _restartPending) != 0
|| (_restartThread is not null
&& _restartThread.IsAlive
&& _restartThread != Thread.CurrentThread);
}
public static void DoExit(int exitcode = 0)
{
WriteBackSettings();
ConsoleIO.WriteLineFormatted("§a" + string.Format(Translations.config_saving, settingsIniPath));
if (Interlocked.Exchange(ref _exitInProgress, 1) != 0)
return;
Interlocked.Exchange(ref _failureInProgress, 1);
Interlocked.Exchange(ref _restartInProgress, 1);
try
{
WriteBackSettings();
ConsoleIO.WriteLineFormatted("§a" + string.Format(Translations.config_saving, settingsIniPath));
}
catch { }
try
{
McClient? activeClient = client;
if (activeClient is not null)
{
activeClient.Disconnect();
if (ReferenceEquals(client, activeClient))
client = null;
ConsoleIO.Reset();
}
}
catch { }
if (client is not null) { client.Disconnect(); ConsoleIO.Reset(); }
if (offlinePrompt is not null)
{
if (ConsoleIO.Backend is not null)
ConsoleIO.Backend.OnInputChange -= ConsoleIO.OfflineAutocompleteHandler;
offlinePrompt.Item2.Cancel();
if (Thread.CurrentThread != offlinePrompt.Item1)
offlinePrompt.Item1.Join(1000);
offlinePrompt = null;
ConsoleIO.Reset();
try
{
if (ConsoleIO.Backend is not null)
ConsoleIO.Backend.OnInputChange -= ConsoleIO.OfflineAutocompleteHandler;
offlinePrompt.Item2.Cancel();
if (offlinePrompt.Item1.IsAlive)
{
if (!offlinePrompt.Item1.Join(TimeSpan.FromSeconds(1)))
offlinePrompt.Item1.Interrupt();
}
}
catch { }
finally
{
offlinePrompt = null;
ConsoleIO.Reset();
}
}
if (Config.Main.Advanced.PlayerHeadAsIcon && OperatingSystem.IsWindows()) { ConsoleIcon.RevertToMCCIcon(); }
ConsoleIO.Backend?.Shutdown();
try { ConsoleIO.Backend?.Shutdown(); } catch { }
Interlocked.Exchange(ref _failureInProgress, 0);
Interlocked.Exchange(ref _restartInProgress, 0);
Interlocked.Exchange(ref _exitInProgress, 0);
Environment.Exit(exitcode);
}
@ -988,7 +1119,7 @@ namespace MinecraftClient
/// </summary>
public static void Exit(int exitcode = 0)
{
new Thread(() => { DoExit(exitcode); }).Start();
DoExit(exitcode);
}
/// <summary>
@ -1000,143 +1131,158 @@ namespace MinecraftClient
/// <param name="disconnectReason">If set, the error message will be processed by the AutoRelog bot</param>
public static void HandleFailure(string? errorMessage = null, bool versionError = false, ChatBot.DisconnectReason? disconnectReason = null)
{
bool autoRelogHandled = false;
if (Volatile.Read(ref _restartInProgress) != 0 || Volatile.Read(ref _restartPending) != 0)
return;
if (!string.IsNullOrEmpty(errorMessage))
if (Interlocked.Exchange(ref _failureInProgress, 1) != 0)
return;
try
{
ConsoleIO.Reset();
if (ConsoleIO.Backend is not Tui.TuiConsoleBackend)
bool autoRelogHandled = false;
if (!string.IsNullOrEmpty(errorMessage))
{
try
ConsoleIO.Reset();
if (ConsoleIO.Backend is not Tui.TuiConsoleBackend)
{
while (Console.KeyAvailable)
Console.ReadKey(true);
}
catch { }
}
ConsoleIO.WriteLine(errorMessage);
if (disconnectReason.HasValue)
{
autoRelogHandled = true;
if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage))
return;
}
}
if (InternalConfig.InteractiveMode)
{
if (versionError)
{
ConsoleIO.WriteLine(Translations.mcc_server_version);
InternalConfig.MinecraftVersion = ConsoleIO.ReadLine();
if (InternalConfig.MinecraftVersion != "")
{
useMcVersionOnce = true;
Restart(0, true);
return;
}
}
if (!autoRelogHandled && disconnectReason.HasValue)
{
if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage!))
return;
}
if (offlinePrompt is null)
{
ConsoleIO.Backend?.StopReadThread();
if (ConsoleIO.Backend is not null)
ConsoleIO.Backend.OnInputChange += ConsoleIO.OfflineAutocompleteHandler;
var cancellationTokenSource = new CancellationTokenSource();
offlinePrompt = new(new Thread(new ThreadStart(delegate
{
bool exitThread = false;
string command = " ";
ConsoleIO.WriteLine(string.Empty);
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_disconnected, Config.Main.Advanced.InternalCmdChar.ToLogString()));
if (ConsoleIO.Backend is Tui.TuiConsoleBackend)
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_use_quit_to_exit, Config.Main.Advanced.InternalCmdChar.ToLogString()));
else
ConsoleIO.WriteLineFormatted(Translations.mcc_press_exit, acceptnewlines: true);
while (!cancellationTokenSource.IsCancellationRequested)
try
{
if (exitThread)
return;
command = ConsoleIO.ReadLine().Trim();
if (command.Length == 0)
{
if (ConsoleIO.Backend is not Tui.TuiConsoleBackend)
Commands.Exit.DoExit(Config.AppVar.ExpandVars(command));
continue;
}
string message = "";
if (Config.Main.Advanced.InternalCmdChar.ToChar() != ' '
&& command[0] == Config.Main.Advanced.InternalCmdChar.ToChar())
command = command[1..];
if (command.StartsWith("reco"))
{
message = Commands.Reco.DoReconnect(Config.AppVar.ExpandVars(command));
if (message == "")
{
exitThread = true;
continue;
}
}
else if (command.StartsWith("connect"))
{
message = Commands.Connect.DoConnect(Config.AppVar.ExpandVars(command));
if (message == "")
{
exitThread = true;
continue;
}
}
else if (command.StartsWith("exit") || command.StartsWith("quit"))
{
message = Commands.Exit.DoExit(Config.AppVar.ExpandVars(command));
}
else if (command.StartsWith("help"))
{
ConsoleIO.WriteLineFormatted("§8MCC: " +
Config.Main.Advanced.InternalCmdChar.ToLogString() +
new Commands.Reco().GetCmdDescTranslated());
ConsoleIO.WriteLineFormatted("§8MCC: " +
Config.Main.Advanced.InternalCmdChar.ToLogString() +
new Commands.Connect().GetCmdDescTranslated());
}
else
ConsoleIO.WriteLineFormatted(string.Format(Translations.icmd_unknown, command.Split(' ')[0]));
if (message != "")
ConsoleIO.WriteLineFormatted("§8MCC: " + message);
while (Console.KeyAvailable)
Console.ReadKey(true);
}
})), cancellationTokenSource);
offlinePrompt.Item1.Start();
}
}
else
{
// Not in interactive mode, just exit and let the calling script handle the failure
if (disconnectReason.HasValue)
{
// Return distinct exit codes for known failures.
if (disconnectReason.Value == ChatBot.DisconnectReason.UserLogout) Exit(1);
if (disconnectReason.Value == ChatBot.DisconnectReason.InGameKick) Exit(2);
if (disconnectReason.Value == ChatBot.DisconnectReason.ConnectionLost) Exit(3);
if (disconnectReason.Value == ChatBot.DisconnectReason.LoginRejected) Exit(4);
}
Exit();
}
catch { }
}
ConsoleIO.WriteLine(errorMessage);
if (disconnectReason.HasValue)
{
autoRelogHandled = true;
if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage))
return;
}
}
if (InternalConfig.InteractiveMode)
{
if (versionError)
{
ConsoleIO.WriteLine(Translations.mcc_server_version);
InternalConfig.MinecraftVersion = ConsoleIO.ReadLine();
if (InternalConfig.MinecraftVersion != "")
{
useMcVersionOnce = true;
Restart(0, true);
return;
}
}
if (!autoRelogHandled && disconnectReason.HasValue)
{
if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage!))
return;
}
if (offlinePrompt is null)
{
ConsoleIO.Backend?.StopReadThread();
if (ConsoleIO.Backend is not null)
ConsoleIO.Backend.OnInputChange += ConsoleIO.OfflineAutocompleteHandler;
var cancellationTokenSource = new CancellationTokenSource();
offlinePrompt = new(new Thread(new ThreadStart(delegate
{
bool exitThread = false;
string command = " ";
ConsoleIO.WriteLine(string.Empty);
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_disconnected, Config.Main.Advanced.InternalCmdChar.ToLogString()));
if (ConsoleIO.Backend is Tui.TuiConsoleBackend)
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_use_quit_to_exit, Config.Main.Advanced.InternalCmdChar.ToLogString()));
else
ConsoleIO.WriteLineFormatted(Translations.mcc_press_exit, acceptnewlines: true);
while (!cancellationTokenSource.IsCancellationRequested)
{
if (exitThread)
return;
command = ConsoleIO.ReadLineWithTimeout(TimeSpan.FromSeconds(1)).Trim();
if (command.Length == 0)
{
if (ConsoleIO.Backend is not Tui.TuiConsoleBackend)
{
try { Commands.Exit.DoExit(Config.AppVar.ExpandVars(command)); }
catch { }
}
continue;
}
string message = "";
if (Config.Main.Advanced.InternalCmdChar.ToChar() != ' '
&& command[0] == Config.Main.Advanced.InternalCmdChar.ToChar())
command = command[1..];
if (command.StartsWith("reco"))
{
message = Commands.Reco.DoReconnect(Config.AppVar.ExpandVars(command));
if (message == "")
{
exitThread = true;
continue;
}
}
else if (command.StartsWith("connect"))
{
message = Commands.Connect.DoConnect(Config.AppVar.ExpandVars(command));
if (message == "")
{
exitThread = true;
continue;
}
}
else if (command.StartsWith("exit") || command.StartsWith("quit"))
{
message = Commands.Exit.DoExit(Config.AppVar.ExpandVars(command));
}
else if (command.StartsWith("help"))
{
ConsoleIO.WriteLineFormatted("§8MCC: " +
Config.Main.Advanced.InternalCmdChar.ToLogString() +
new Commands.Reco().GetCmdDescTranslated());
ConsoleIO.WriteLineFormatted("§8MCC: " +
Config.Main.Advanced.InternalCmdChar.ToLogString() +
new Commands.Connect().GetCmdDescTranslated());
}
else
ConsoleIO.WriteLineFormatted(string.Format(Translations.icmd_unknown, command.Split(' ')[0]));
if (message != "")
ConsoleIO.WriteLineFormatted("§8MCC: " + message);
}
})), cancellationTokenSource);
offlinePrompt.Item1.Start();
}
}
else
{
// Not in interactive mode, just exit and let the calling script handle the failure
if (disconnectReason.HasValue)
{
// Return distinct exit codes for known failures.
if (disconnectReason.Value == ChatBot.DisconnectReason.UserLogout) Exit(1);
if (disconnectReason.Value == ChatBot.DisconnectReason.InGameKick) Exit(2);
if (disconnectReason.Value == ChatBot.DisconnectReason.ConnectionLost) Exit(3);
if (disconnectReason.Value == ChatBot.DisconnectReason.LoginRejected) Exit(4);
}
Exit();
}
}
finally
{
Interlocked.Exchange(ref _failureInProgress, 0);
}
}
/// <summary>