Giter Site home page Giter Site logo

Comments (8)

hguy avatar hguy commented on September 27, 2024 1

This solution did work for a time and it start crashing again. I give up :p
There is some random weirdness i can't comprehend in this and it's not that necessary.
Thanks for your help though.

from medallionshell.

madelson avatar madelson commented on September 27, 2024

One thing I can think of is that .Wait() is blocking, which is generally not good to do on the UI thread. You could even be hitting sync-over-async deadlock. Try await command.Task; instead.

If that doesn't work:

  • Can you post the relevant code here (how you create the command, how you interact with and wait for it, etc)
  • When you say "crash", what happens exactly? Do you get a crash dump? Does the process exit? Does it hang?

from medallionshell.

hguy avatar hguy commented on September 27, 2024

Unfortunately i had to refactor for now.
But here's the code at the time (thanks git) , just to give you an idea.

GitAddCommitTagAndPush() is called in the BackgroundWorker.DoWork().

In here, UIService.ProgressBarReport(); is raising an event that update the form (BackgroundWorker.ReportProgress()), but i tried many things.

The code can debug step by step without problem until i hit the first Command.Wait() at step git status -suall.

The application crash like

  • the form close instantly
  • VS 2022 lose debug session back to code editing
  • no catch
  • no logs
	IProgress<(string Title, int Percent)> GitAddCommitTagAndPushProgress;
	public bool GitAddCommitTagAndPush()
	{
		if (Config.UserSettings.Default.GitBackupEnabled && GamePathService.LocalGitRepositoryGitDirExist)
		{
			var repoUrl = Config.UserSettings.Default.GitBackupRepository;
			string errMess = string.Format(Resources.GitUnableToPush, repoUrl);
			Action<Shell.Options> options = (opt) => opt.WorkingDirectory(GamePathService.LocalGitRepositoryDirectory);

			Command status, stageAll, commit, tagVersion, tagLatest, push;
			status = stageAll = commit = tagVersion = tagLatest = push = null;

			try
			{
				
				/*
				 * Reporting synchronously via event starts good but break at some point, job is done though.
				 * 
				 * IProgress : Should be the new way of reporting progress. 
				 * It report progress on time and is not blocking, but the Form is delaying result processing and flush them all AFTER the job. 
				 * 
				 * BackgroundWorker.ReportProgress() : should work for Winform 
				 * but Command.Wait() inside BackgroundWorker.DoWork() simply brutaly kill the app, like a BSOD for app. LOL
				 * 
				 * I really tried.
				 * hguy
				 */


				GitAddCommitTagAndPushProgress = UIService.ProgressBarShow().Progress; 
				// GitAddCommitTagAndPushProgress.Report(("Git Status", 1));
				UIService.ProgressBarReport("Git Status", 1);

				// Is there anything new to commit ?
				status = Command.Run("git.exe", new[] { "status", "-suall" }, options);
				var statusSuccess = HandleExecuteOut(status, errMess, out var statusoutStd, out var statuserrStd);
				if (statusSuccess && statusoutStd.Count > 0)
				{
					UIService.ProgressBarReport("Git Stage", 20);
					//GitAddCommitTagAndPushProgress.Report(("Git Stage", 20));
					stageAll = Command.Run("git.exe", new[] { "stage", "*" }, options);
					if (HandleExecuteOut(stageAll, errMess, out var stageAlloutStd, out var stageAllerrStd))
					{
						UIService.ProgressBarReport("Git Commit", 40);
						//GitAddCommitTagAndPushProgress.Report(("Git Commit", 40));
						commit = Command.Run("git.exe", new[] { "commit", "-m", @"TQVaultAE update!" }, options);
						if (HandleExecuteOut(commit, errMess, out var commitoutStd, out var commiterrStd))
						{
							var now = DateTime.Now;
							var nbrOfSecToday = now - new DateTime(now.Year, now.Month, now.Day);
							var tag = now.ToString("yy.MM.dd") + '.' + (int)nbrOfSecToday.TotalSeconds;// Use date for versioning

							UIService.ProgressBarReport("Git Tags", 60);
							//GitAddCommitTagAndPushProgress.Report(("Git Tags", 60));
							tagVersion = Command.Run("git.exe", new[] { "tag", tag, }, options);
							if (HandleExecuteOut(tagVersion, errMess, out var tagVersionoutStd, out var tagVersionerrStd))
							{
								tagLatest = Command.Run("git.exe", new[] { "tag", "-f", "latest" }, options);
								if (HandleExecuteOut(tagLatest, errMess, out var tagLatestoutStd, out var tagLatesterrStd))
								{
									//GitAddCommitTagAndPushProgress.Report(("Git Push", 0));
									UIService.ProgressBarReport("Git Push", 80);
									using (push = Command.Run("git.exe", new[] { "push", "-v", "--progress" } // You need "--progress" to capture percentage in StandardError
										, (opt) =>
										{
											opt.WorkingDirectory(GamePathService.LocalGitRepositoryDirectory);
											//opt.DisposeOnExit(false); // needed for RedirectStandardError = true
											//opt.StartInfo(si => { si.RedirectStandardError = true; }); // needed for ConsumeStandardOutputAsync
										})
									)
									{
										// Hook console output verbosity

										// --- Method StandardError direct reading
										//var tsk = Task.Run(() => ConsumeStandardOutputAsync(push.StandardError));
										//push.Wait();
										//return push.Result.Success;

										// --- Method BindingList event model
										//BindingList<string> pushoutStd = new(), pusherrStd = new();
										//pusherrStd.ListChanged += PusherrStd_ListChanged;
										//var res = HandleExecuteRef(push, errMess, ref pushoutStd, ref pusherrStd);
										//return res;

										return HandleExecuteOut(tagLatest, errMess, out var pushoutStd, out var pusherrStd);
									}
								}
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				Log.LogError(ex, "Git Backup : Push failed!");
			}
			finally
			{
				// ProgressBarHide
				UIService.ProgressBarHide();
			}

		}
		return false;
	}

	#region MedallionShell output hook

	private void PusherrStd_ListChanged(object sender, ListChangedEventArgs e)
	{
		var lst = sender as BindingList<string>;

		if (e.ListChangedType == ListChangedType.ItemAdded)
		{
			var line = lst[e.NewIndex];
			if (line is not null && PercentProgressRegEx.Match(line) is { Success: true } m)
			{
				var num = m.Groups["Num"].Value;
				//Debug.WriteLine("Num : " + num);
				var percent = int.Parse(num);
				//UIService.ProgressBarReport("Git Push : " + line, percent);// UI hang
				//GitAddCommitTagAndPushProgress.Report(("Git Push : " + line, percent));// Do report in time but everything is flushed after work.
			}
		}
	}

	private async void ConsumeStandardOutputAsync(ProcessStreamReader output)
	{
		// From https://github.com/madelson/MedallionShell/issues/23
		string line;
		while ((line = await output.ReadLineAsync().ConfigureAwait(false)) != null)
		{
			Console.WriteLine(line);
			/* Will match
			Counting objects: 100% (2173/2173), done.
			Compressing objects: 100% (2117/2117), done.
			Writing objects: 100% (2172/2172), 38.08 MiB | 2.23 MiB/s, done.
			remote: Resolving deltas: 100% (1886/1886), done.
			*/
			if (line is not null && PercentProgressRegEx.Match(line) is { Success: true } m)
			{
				var percent = int.Parse(m.Groups["Num"].Value);
				UIService.ProgressBarReport("Git Push", percent);
			}
		}
	}

	static Regex PercentProgressRegEx = new Regex(@"(?<Num>\d{1,3})%", RegexOptions.Compiled);

	#endregion


	protected bool HandleExecuteOut(Command cmd, string errMess, out BindingList<string> outputLines, out BindingList<string> errorLines, bool pipeOutputStandard = true)
	{
		outputLines = new BindingList<string>();
		errorLines = new BindingList<string>();

		return HandleExecuteRef(cmd, errMess, ref outputLines, ref errorLines, pipeOutputStandard);
	}

	protected bool HandleExecuteRef(Command cmd, string errMess, ref BindingList<string> outputLines, ref BindingList<string> errorLines, bool pipeOutputStandard = true)
	{
		string errLog = string.Empty;
		try
		{
			if (pipeOutputStandard)
			{
				cmd.StandardOutput.PipeToAsync(outputLines);
				cmd.StandardError.PipeToAsync(errorLines);
			}
			cmd.Wait();

			if (!cmd.Result.Success)
				errLog = errorLines.JoinString(Environment.NewLine);

			return cmd.Result.Success;
		}
		catch (Exception ex)
		{
			this.Log.LogError(ex, errMess);
			errLog = ex.Message;
		}
		finally
		{
			if (errLog != string.Empty)
			{
				errMess += Environment.NewLine + errLog;
				this.Log.LogError(errMess);
				this.UIService.ShowError(errMess, Buttons: ShowMessageButtons.OK);
			}
		}
		return false;
	}

from medallionshell.

madelson avatar madelson commented on September 27, 2024

Hmm a crash like that makes me think that you are somehow hitting a StackOverflow or some other very serious exception. Of course without a full repro I'm not going to be able to test that. With StackOverflow, often if you capture a crash dump you can see what is going on in windbg or other dump analysis tools.

Some other thoughts about the code:

  • Can you confirm that calling GamePathService.LocalGitRepositoryDirectory succeeds? With the code as shown this doesn't get called until you run the command.
  • HandleExecuteRef pipes stdout/stderr via PipeAsync, but doesn't await those tasks. If BindingList<T> isn't thread-safe, you might run into issues where the writer is still piping when you are trying to read those collections.

from medallionshell.

hguy avatar hguy commented on September 27, 2024
  • Can you confirm that calling GamePathService.LocalGitRepositoryDirectory succeeds? With the code as shown this doesn't get called until you run the command.

Yes GamePathService.LocalGitRepositoryDirectory is nothing special, just a Path.Combine().

  • HandleExecuteRef pipes stdout/stderr via PipeAsync, but doesn't await those tasks. If BindingList<T> isn't thread-safe, you might run into issues where the writer is still piping when you are trying to read those collections.

BindingList<T> isn't thread-safe but i don't loop over it, just react to an event that cherry pick an element that i'm sure exists in the collection. That was not a problem when i reported via IProcess<> which is thread safe.
Using IProcess<> was the most successful workflow until it sync with Winform , the last step.

Isn't it Command.Wait() that do the waiting on stdout/stderr via PipeAsync ?

from medallionshell.

hguy avatar hguy commented on September 27, 2024
  • HandleExecuteRef pipes stdout/stderr via PipeAsync, but doesn't await those tasks. If BindingList<T> isn't thread-safe, you might run into issues where the writer is still piping when you are trying to read those collections.

Just get one case. i did add the .Wait() after PipeAsync. Thanks for the tip.

			Task stdout = Task.CompletedTask, stderr = Task.CompletedTask, fullTask;
			if (pipeOutputStandard)
			{
				stdout = cmd.StandardOutput.PipeToAsync(outputLines);
				stderr = cmd.StandardError.PipeToAsync(errorLines);
			}

			fullTask = Task.WhenAll(stdout, stderr, cmd.Task);
			fullTask.Wait();

from medallionshell.

madelson avatar madelson commented on September 27, 2024

Seems like a good change, but I assume this doesn't fix the issue?

If it is still crashing, have you gathered a crash dump (see https://michaelscodingspot.com/how-to-create-use-and-debug-net-application-crash-dumps-in-2019/)?

from medallionshell.

hguy avatar hguy commented on September 27, 2024

After my various refacto i didn't reproduce the crash but i finaly succeed.

This construct by doing a mix of BackgroundWorker and IProgress do work.

Inside GitAddCommitTagAndPush() i use the BindingList<> hook from previous post.

I ask IProgress to call backgroundWorker.ReportProgress() so my underlying layers are agnostic and doesn't know System.Windows.Form.

Thank you for your help!

	/// <summary>
	/// Handler for closing the main form
	/// </summary>
	/// <param name="sender">sender object</param>
	/// <param name="e">CancelEventArgs data</param>
	private void MainFormClosing(object sender, CancelEventArgs e)
	{
		if (!_DoCloseStuffCompleted)
		{
			e.Cancel = !this.DoCloseStuff();
			if (e.Cancel) return;// Problem ? no need to go further

			if (Config.UserSettings.Default.GitBackupEnabled)
			{
				var pb = this.vaultProgressBar;
				pb.BringToFront();
				pb.Minimum =
				pb.Value = 0;
				pb.Maximum = 100;
				pb.TitleForeColor = TQColor.Purple.Color();
				pb.TitleFont = FontService.GetFont(15F, this.UIService.Scale);

				var x = (this.Size.Width / 2) - (this.vaultProgressBar.Width / 2);
				var y = (this.Size.Height / 2);
				var loc = new Point(x, y);
				pb.Location = loc;
				pb.Visible = true;

				// IProgress
				var progress = new Progress<ProgressBarMessage>((mess) =>
				{
					this.backgroundWorkerGit.ReportProgress(mess.Percent, mess);
				});

				this.backgroundWorkerGit.RunWorkerAsync(progress);
			}
			return;
		}
	}

	bool _DoCloseStuffCompleted = false;

	private void backgroundWorkerGit_DoWork(object sender, DoWorkEventArgs e)
	{
		var arg = e.Argument as Progress<ProgressBarMessage>;
		this.GameFileService.GitAddCommitTagAndPush(arg);
	}

	private void backgroundWorkerGit_ProgressChanged(object sender, ProgressChangedEventArgs e)
	{
		var pb = this.vaultProgressBar;
		var mess = e.UserState as ProgressBarMessage;
		pb.Title = mess.Title;
		pb.Value = e.ProgressPercentage;
		pb.Invalidate();
	}

	private void backgroundWorkerGit_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
	{
		var pb = this.vaultProgressBar;
		pb.Visible = false;
		pb.SendToBack();

		_DoCloseStuffCompleted = true;
		this.Close();// Close again but it will pass
	}

from medallionshell.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.