Comments (8)
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.
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.
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.
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. IfBindingList<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.
- 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. IfBindingList<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.
HandleExecuteRef
pipes stdout/stderr via PipeAsync, but doesn't await those tasks. IfBindingList<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.
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.
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)
- Open files in async mode when piping
- Consider moving IsExpectedPipeException into ProcessStreamWrapper
- Consider optimizing read buffering with a "keeping up" model
- How to Filter console output HOT 3
- Command.Result blocks indefinitely in xunit HOT 14
- Out of Memory Exception, Consuming stream and Timeout HOT 5
- Cannot launch process through conhost.exe HOT 4
- Please upload release tags HOT 1
- PipeTo does not work as expected HOT 2
- Can not redirect shell command 'read' HOT 5
- Threading problem, locks other instance files HOT 6
- how can i do multiple input stream? HOT 5
- MedallionShell memory leak HOT 3
- Redirecting stdout/stderr blocks a thread pool thread for each call HOT 6
- In .NET5+ projects one could use ProcessStartInfo.Arguments HOT 2
- Leverage ProcessStartInfo.StandardInputEncoding on frameworks that support it
- Leverage Span/Memory on all streams/writers/readers in frameworks that support it
- MergedLinesEnumerable should implement IAsyncEnumerable HOT 1
- Leverage ArrayPool where applicable
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from medallionshell.