Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.

Commit c32060d

Browse files
committed
Fix a bunch of bugs in the task system related to tasks that run on failure
1 parent 79cedd6 commit c32060d

File tree

5 files changed

+120
-94
lines changed

5 files changed

+120
-94
lines changed

src/GitHub.Api/Application/ApplicationManagerBase.cs

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -180,29 +180,24 @@ private void InitializeEnvironment(NPath gitExecutablePath)
180180

181181
if (Environment.IsWindows)
182182
{
183-
GitClient
183+
var task = GitClient
184184
.GetConfig("credential.helper", GitConfigSource.Global)
185185
.Then((b, credentialHelper) => {
186-
if (!string.IsNullOrEmpty(credentialHelper))
186+
if (string.IsNullOrEmpty(credentialHelper))
187187
{
188-
Logger.Trace("Windows CredentialHelper: {0}", credentialHelper);
189-
afterGitSetup.Start();
188+
Logger.Warning("No Windows CredentialHelper found: Setting to wincred");
189+
throw new ArgumentNullException(nameof(credentialHelper));
190190
}
191-
else
192-
{
193-
Logger.Warning("No Windows CredentialHeloper found: Setting to wincred");
191+
});
192+
// if there's no credential helper, set it before restarting the repository
193+
task.Then(GitClient.SetConfig("credential.helper", "wincred", GitConfigSource.Global), TaskRunOptions.OnFailure)
194+
.Then(afterGitSetup, taskIsTopOfChain: true);
194195

195-
GitClient.SetConfig("credential.helper", "wincred", GitConfigSource.Global)
196-
.Then(afterGitSetup)
197-
.Start();
198-
}
199-
})
200-
.Start();
201-
}
202-
else
203-
{
204-
afterGitSetup.Start();
196+
// if there's a credential helper, we're good, restart the repository
197+
task.Then(afterGitSetup, TaskRunOptions.OnSuccess, taskIsTopOfChain: true);
205198
}
199+
200+
afterGitSetup.Start();
206201
}
207202

208203
private bool disposed = false;

src/GitHub.Api/Tasks/TaskBase.cs

Lines changed: 50 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public enum TaskRunOptions
1414

1515
public interface ITask : IAsyncResult
1616
{
17-
T Then<T>(T continuation, TaskRunOptions runOptions = TaskRunOptions.OnSuccess) where T : ITask;
17+
T Then<T>(T continuation, TaskRunOptions runOptions = TaskRunOptions.OnSuccess, bool taskIsTopOfChain = false) where T : ITask;
1818
ITask Catch(Action<Exception> handler);
1919
ITask Catch(Func<Exception, bool> handler);
2020
/// <summary>
@@ -24,7 +24,7 @@ public interface ITask : IAsyncResult
2424
/// <summary>
2525
/// Run a callback at the end of the task execution, on a separate thread, regardless of execution state
2626
/// </summary>
27-
ITask Finally(Action<bool, Exception> actionToContinueWith, TaskAffinity affinity);
27+
ITask Finally(Action<bool, Exception> actionToContinueWith, TaskAffinity affinity = TaskAffinity.Concurrent);
2828
/// <summary>
2929
/// Run another task at the end of the task execution, on a separate thread, regardless of execution state
3030
/// </summary>
@@ -63,11 +63,11 @@ public interface ITask<TResult> : ITask
6363
/// <summary>
6464
/// Run a callback at the end of the task execution, on a separate thread, regardless of execution state
6565
/// </summary>
66-
ITask<TResult> Finally(Func<bool, Exception, TResult, TResult> continuation, TaskAffinity affinity);
66+
ITask<TResult> Finally(Func<bool, Exception, TResult, TResult> continuation, TaskAffinity affinity = TaskAffinity.Concurrent);
6767
/// <summary>
6868
/// Run a callback at the end of the task execution, on a separate thread, regardless of execution state
6969
/// </summary>
70-
ITask Finally(Action<bool, Exception, TResult> continuation, TaskAffinity affinity);
70+
ITask Finally(Action<bool, Exception, TResult> continuation, TaskAffinity affinity = TaskAffinity.Concurrent);
7171
new ITask<TResult> Start();
7272
new ITask<TResult> Start(TaskScheduler scheduler);
7373
new ITask<TResult> Progress(Action<IProgress> progressHandler);
@@ -93,6 +93,8 @@ public abstract class TaskBase : ITask
9393

9494
protected bool previousSuccess = true;
9595
protected Exception previousException;
96+
protected bool taskFailed = false;
97+
protected bool exceptionWasHandled = false;
9698

9799
protected TaskBase continuationOnSuccess;
98100
protected TaskBase continuationOnFailure;
@@ -147,14 +149,15 @@ protected TaskBase()
147149
this.progress = new Progress { Task = this };
148150
}
149151

150-
public virtual T Then<T>(T nextTask, TaskRunOptions runOptions = TaskRunOptions.OnSuccess)
152+
public virtual T Then<T>(T nextTask, TaskRunOptions runOptions = TaskRunOptions.OnSuccess, bool taskIsTopOfChain = false)
151153
where T : ITask
152154
{
153155
Guard.ArgumentNotNull(nextTask, nameof(nextTask));
154156
var nextTaskBase = ((TaskBase)(object)nextTask);
155157

156158
// find the task at the top of the chain
157-
nextTaskBase = nextTaskBase.GetTopMostTask() ?? nextTaskBase;
159+
if (!taskIsTopOfChain)
160+
nextTaskBase = nextTaskBase.GetTopMostTask() ?? nextTaskBase;
158161
// make the next task dependent on this one so it can get values from us
159162
nextTaskBase.SetDependsOn(this);
160163

@@ -176,7 +179,7 @@ public virtual T Then<T>(T nextTask, TaskRunOptions runOptions = TaskRunOptions.
176179
else if (runOptions == TaskRunOptions.OnFailure)
177180
{
178181
this.continuationOnFailure = nextTaskBase;
179-
DependsOn?.SetFaultHandler(nextTaskBase);
182+
DependsOn?.Then(nextTaskBase, TaskRunOptions.OnFailure, true);
180183
}
181184
else
182185
{
@@ -270,16 +273,9 @@ public ITask Progress(Action<IProgress> handler)
270273

271274
public virtual ITask Start()
272275
{
273-
var depends = GetTopMostTaskInCreatedState();
274-
if (depends != null)
275-
{
276-
depends.Run();
277-
return this;
278-
}
279-
else
280-
{
281-
return TaskManager.Instance.Schedule(this);
282-
}
276+
var depends = GetTopMostTaskInCreatedState() ?? this;
277+
depends.Run();
278+
return this;
283279
}
284280

285281
protected void Run()
@@ -288,10 +284,6 @@ protected void Run()
288284
{
289285
TaskManager.Instance.Schedule(this);
290286
}
291-
else
292-
{
293-
RunContinuation();
294-
}
295287
}
296288

297289
/// <summary>
@@ -304,7 +296,7 @@ protected void Start(Task task)
304296
previousSuccess = task.Status == TaskStatus.RanToCompletion && task.Status != TaskStatus.Faulted;
305297
previousException = task.Exception;
306298
Task.Start(TaskManager.GetScheduler(Affinity));
307-
RunContinuation();
299+
SetContinuation();
308300
}
309301

310302
public virtual ITask Start(TaskScheduler scheduler)
@@ -313,8 +305,8 @@ public virtual ITask Start(TaskScheduler scheduler)
313305
{
314306
//Logger.Trace($"Starting {Affinity} {ToString()}");
315307
Task.Start(scheduler);
308+
SetContinuation();
316309
}
317-
RunContinuation();
318310
return this;
319311
}
320312

@@ -333,33 +325,22 @@ public bool IsChainExclusive()
333325
return DependsOn?.IsChainExclusive() ?? false;
334326
}
335327

336-
protected virtual void RunContinuation()
328+
protected void SetContinuation()
337329
{
338-
if (continuationOnSuccess != null)
339-
{
340-
//Logger.Trace($"Setting ContinueWith {Affinity} {continuation}");
341-
Task.ContinueWith(_ => ((TaskBase)(object)continuationOnSuccess).Run(), Token,
342-
runOnSuccessOptions,
343-
TaskManager.GetScheduler(continuationOnSuccess.Affinity));
344-
}
345-
346-
if (continuationOnFailure != null)
347-
{
348-
//Logger.Trace($"Setting ContinueWith {Affinity} {continuation}");
349-
Task.ContinueWith(_ => ((TaskBase)(object)continuationOnFailure).Run(), Token,
350-
runOnFaultOptions,
351-
TaskManager.GetScheduler(continuationOnFailure.Affinity));
352-
}
353-
354330
if (continuationOnAlways != null)
355331
{
356332
//Logger.Trace($"Setting ContinueWith {Affinity} {continuation}");
357-
Task.ContinueWith(_ => ((TaskBase)(object)continuationOnAlways).Run(), Token,
358-
runAlwaysOptions,
359-
TaskManager.GetScheduler(continuationOnAlways.Affinity));
333+
SetContinuation(continuationOnAlways, runAlwaysOptions);
360334
}
361335
}
362336

337+
protected void SetContinuation(TaskBase continuation, TaskContinuationOptions runOptions)
338+
{
339+
Task.ContinueWith(_ => ((TaskBase)(object)continuation).Run(), Token,
340+
runOptions,
341+
TaskManager.GetScheduler(continuation.Affinity));
342+
}
343+
363344
protected ITask SetDependsOn(ITask dependsOn)
364345
{
365346
DependsOn = (TaskBase)dependsOn;
@@ -414,8 +395,20 @@ protected virtual void RaiseOnStart()
414395
protected virtual void RaiseOnEnd()
415396
{
416397
OnEnd?.Invoke(this);
417-
if (continuationOnSuccess == null && continuationOnFailure == null && continuationOnAlways == null)
418-
CallFinallyHandler();
398+
if (!taskFailed || exceptionWasHandled)
399+
{
400+
if (continuationOnSuccess == null && continuationOnAlways == null)
401+
CallFinallyHandler();
402+
else if (continuationOnSuccess != null)
403+
SetContinuation(continuationOnSuccess, runOnSuccessOptions);
404+
}
405+
else
406+
{
407+
if (continuationOnFailure == null && continuationOnAlways == null)
408+
CallFinallyHandler();
409+
else if (continuationOnFailure != null)
410+
SetContinuation(continuationOnFailure, runOnSuccessOptions);
411+
}
419412
//Logger.Trace($"Finished {ToString()}");
420413
}
421414

@@ -426,16 +419,19 @@ protected void CallFinallyHandler()
426419

427420
protected virtual bool RaiseFaultHandlers(Exception ex)
428421
{
422+
taskFailed = true;
429423
if (catchHandler == null)
430-
return false;
431-
bool handled = false;
424+
return continuationOnFailure != null;
432425
foreach (var handler in catchHandler.GetInvocationList())
433426
{
434-
handled |= (bool)handler.DynamicInvoke(new object[] { ex });
435-
if (handled)
427+
if ((bool)handler.DynamicInvoke(new object[] { ex }))
428+
{
429+
exceptionWasHandled = true;
436430
break;
431+
}
437432
}
438-
return handled;
433+
// if a catch handler returned true or we have a continuation for failure cases, don't throw
434+
return exceptionWasHandled || continuationOnFailure != null;
439435
}
440436

441437
protected Exception GetThrownException()
@@ -527,9 +523,9 @@ protected TaskBase(Task<TResult> task)
527523
}, task, Token, TaskCreationOptions.None);
528524
}
529525

530-
public override T Then<T>(T continuation, TaskRunOptions runOptions = TaskRunOptions.OnSuccess)
526+
public override T Then<T>(T continuation, TaskRunOptions runOptions = TaskRunOptions.OnSuccess, bool taskIsTopOfChain = false)
531527
{
532-
return base.Then<T>(continuation, runOptions);
528+
return base.Then<T>(continuation, runOptions, taskIsTopOfChain);
533529
}
534530

535531
/// <summary>
@@ -635,6 +631,8 @@ protected virtual void RaiseOnEnd(TResult result)
635631
finallyHandler?.Invoke(Task.Status == TaskStatus.RanToCompletion, result);
636632
CallFinallyHandler();
637633
}
634+
else if (continuationOnSuccess != null)
635+
SetContinuation(continuationOnSuccess, runOnSuccessOptions);
638636
//Logger.Trace($"Finished {ToString()} {result}");
639637
}
640638

src/GitHub.Api/Tasks/TaskExtensions.cs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -121,31 +121,19 @@ public static Action Debounce(this Action func, int milliseconds = 300)
121121
};
122122
}
123123

124-
public static ITask Then(this ITask task, Action continuation, TaskRunOptions runOptions = TaskRunOptions.OnSuccess)
125-
{
126-
Guard.ArgumentNotNull(continuation, "continuation");
127-
return task.Then(new ActionTask(task.Token, _ => continuation()) { Name = "Then" }, runOptions);
128-
}
129-
130-
public static ITask Then(this ITask task, Action continuation, TaskAffinity affinity, TaskRunOptions runOptions = TaskRunOptions.OnSuccess)
124+
public static ITask Then(this ITask task, Action continuation, TaskAffinity affinity = TaskAffinity.Concurrent, TaskRunOptions runOptions = TaskRunOptions.OnSuccess)
131125
{
132126
Guard.ArgumentNotNull(continuation, "continuation");
133127
return task.Then(new ActionTask(task.Token, _ => continuation()) { Affinity = affinity, Name = "Then" }, runOptions);
134128
}
135129

136-
public static ITask Then(this ITask task, Action<bool> continuation, TaskRunOptions runOptions = TaskRunOptions.OnSuccess)
137-
{
138-
Guard.ArgumentNotNull(continuation, "continuation");
139-
return task.Then(new ActionTask(task.Token, continuation) { Name = "Then" }, runOptions);
140-
}
141-
142-
public static ITask Then(this ITask task, Action<bool> continuation, TaskAffinity affinity, TaskRunOptions runOptions = TaskRunOptions.OnSuccess)
130+
public static ITask Then(this ITask task, Action<bool> continuation, TaskAffinity affinity = TaskAffinity.Concurrent, TaskRunOptions runOptions = TaskRunOptions.OnSuccess)
143131
{
144132
Guard.ArgumentNotNull(continuation, "continuation");
145133
return task.Then(new ActionTask(task.Token, continuation) { Affinity = affinity, Name = "Then" }, runOptions);
146134
}
147135

148-
public static ITask Then<T>(this ITask task, ActionTask<T> nextTask, T valueForNextTask, TaskRunOptions runOptions = TaskRunOptions.OnSuccess)
136+
public static ITask Then<T>(this ITask task, ActionTask<T> nextTask, T valueForNextTask, TaskAffinity affinity = TaskAffinity.Concurrent, TaskRunOptions runOptions = TaskRunOptions.OnSuccess)
149137
{
150138
Guard.ArgumentNotNull(nextTask, nameof(nextTask));
151139
nextTask.PreviousResult = valueForNextTask;

src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ private void Pull()
677677
// whether pull triggered a merge or a rebase, and abort the operation accordingly
678678
// (either git rebase --abort or git merge --abort)
679679
}
680-
}, TaskRunOptions.OnAlways)
680+
}, runOptions: TaskRunOptions.OnAlways)
681681
.FinallyInUI((success, e) => {
682682
if (success)
683683
{

0 commit comments

Comments
 (0)