-
Notifications
You must be signed in to change notification settings - Fork 585
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Run final targets after user cancels with ctrl+c #1946
Conversation
Clever, I had a similar idea in mind (just killing all processes). The only thing this doesn't handle is when the target function itself is still doing something (and not waiting for external functions) or if the target is just running the next process after we killed the previous one (some tasks have an internal retry...). In fact, I feel like in practice the solution you propose is already more than useful - maybe we can kind of fall-back to our current behavior when the user presses |
What is the current behavior? Istn that just letting the shell kill the process? Or is there already some cleanup-code i did not manage to find? |
No I think the shell is just killing stuff. However if msbuild is running it prints "attempting to kill..." or something like that so the signal already seems to flow though fake (but on the other side I have no idea how things work behind the scenes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks very very reasonable. Some suggestions/question in-line
src/app/Fake.Core.Target/Target.fs
Outdated
f a) | ||
|
||
let cancelHandler = captureContext (handleUserCancelEvent cts) | ||
use __ = Console.CancelKeyPress |> Observable.subscribeOnce cancelHandler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this throw in non-interactive mode (CI systems?) or just do nothing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont know. As no one is pressing any keys there, that should just work. Do you have any idea on how to test that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can keep this in mind and test on staging
src/app/Fake.Core.Target/Target.fs
Outdated
@@ -584,15 +596,16 @@ module Target = | |||
member __.GetNextTarget (ctx) = mbox.PostAndAsyncReply(fun reply -> GetNextTarget(ctx, reply)) | |||
} | |||
|
|||
let runOptimal workerNum (order:Target[] list) targetContext = | |||
let runOptimal workerNum (order:Target[] list) targetContext= |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add that space again :)
src/app/Fake.Core.Target/Target.fs
Outdated
@@ -159,10 +167,12 @@ module Target = | |||
Trace.traceError <| sprintf " - %s" target.Value.Name | |||
failwithf "Target \"%s\" is not defined." name | |||
|
|||
let internal runSimpleInternal context target = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
space
src/app/Fake.Core.Target/Target.fs
Outdated
Arguments : string list | ||
IsRunningFinalTargets : bool | ||
CancellationToken : CancellationToken } | ||
static member Create ft all args isRunningFinalTargets (token:CancellationToken)= { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just start with IsRunningFinalTargets = false
? (also spacing around =
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need a formatting tool ;). you mean IsRunningFinalTargets as the first argument? Is the bool OK or should that be an enum with all the possible states?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bool is okish, but Create
doesn't need it as argument is what I suggest
src/app/Fake.Core.Target/Target.fs
Outdated
member __.OnCompleted()= | ||
remove() | ||
} | ||
observable.Subscribe observer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is basically observable.Take(1)
, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, should be Take(1)/First. But i did not want to add an extra library
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this has a race when the OnNext
event is triggered before setRemover
is called
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to find a suggestion ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isnt this pretty much the same as here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and in fact I feel like it has the same problem. It probably will just work for our use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/fsprojects/FSharp.Control.Reactive supports netstandard2.0 now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes actually I have no problem with adding that dependency as I'm not able to come up with a solution and give up :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How important is .netstandard1.6 support? I would have to remove that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure we can live without netstandard1.6
src/app/Fake.Core.Target/Target.fs
Outdated
// always try to keep "parallelJobs" runners busy | ||
ParallelRunner.runOptimal parallelJobs order context | ||
//order | ||
// |> Seq.fold (fun context par -> runTargetsParallel parallelJobs par context) context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can remove this now as well as the implementation if it still exists (though I can see if you don't want to do it as part of this PR - I wouldn't mind if we already do changes here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Take this with caution:) Maybe we even can unify this now with the other branch (though I'm not sure if that actually looks better)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean remove the ParallelRunner.runOptimal method and the whole if parallelJobs > 1 && not singleTarget
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would then remove the whole ParallelRunner as far as i can see.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I meant the comments and runTargetsParallel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, i can remove those. That confused me ;)
let mutable finalTargetResult = 0 | ||
Target.createFinal "Final" (fun _ -> finalTargetResult <- 1) | ||
|
||
let context = run "c" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should let some tests run with a parallel version of run
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was actually going to add tests for the cancellation, but i am not sure how to do that. It would probaby have to run in another Process? And since i did not really manage to do that i at least added some tests for the normal final-targets :/.
CoreFx has some for cancellation here. But that is quite a lot of boilerblate to add.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think cancellation is fine, I meant is just letting it run through the parallel code paths and see if the result still is fine (yes in fact that might mean that stuff is our of order if dependencies allow it but we can still do basic checks)
let mutable failureTargetResult = 0 | ||
Target.createBuildFailure "FailureTarget" (fun _ -> failureTargetResult <- 1) | ||
Target.createBuildFailure "FailureTarget2" (fun _ -> failureTargetResult <- failureTargetResult+1) | ||
Target.activateBuildFailure "FailureTarget" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understand we have to activate manually because people use this as part of other targets (it they activate FailureTarget
as part of target b
for example.
In an ideal world I'm thinking about doing that over the context and not via global mutable state - and in fact "freeze" the state once "run" is called. However I don't think we can do this at this point without breaking change.
Sadly that one slipped through when re-designing the API... Something to keep in mind for fake 6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need tests then to cover activation in another target? That would also only work at specific timings, so yeah that looks kinda strange.
src/app/Fake.Core.Target/Target.fs
Outdated
let mgr = createCtxMgr order targetContext | ||
let targetRunner () = | ||
async { | ||
let token = targetContext.CancellationToken |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using the token from the context here, should i add it to Async.StartAsTask
and Async.RunSynchronously
? I guess i would have to add it to both because of the Task.AwaitAll?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think it is fine as it is, using the default cancellation token has funny side effects. We want the runner to stay alive in order to execute the final targets.
I'll merge this and some some tests for the parallel branch. Or are you still working on something right now? |
yes, i am fixing all the stuff we discussed |
I am running the build locally, if that passes i will commit. Do you want a new commit so you can see the changes easier? Otherwise i will ammend. |
As you like ;) More commits lead to more contribution points on github :P |
Everything for internetpoints! |
Things we could still do:
|
Ok let's see, nice stuff! |
As a side note for the tests with expecto: have you looked at https://github.com/YoloDev/YoloDev.Expecto.TestSdk ? No idea how good that is, but some people seem to be using it for dotnet core. |
Relevant to your process-tree comment: |
This is what some of the dotnet people are using "solve" that. |
@kblohm Looks interesting, we might want to "save" that in a new issue. But as long as we have no reports about it ;) |
cough #1427
But yeah, I never did send a PR |
Theoretically, if someone might want to start on this, what would the design be?
|
Yes or we just change the default behavior and don't make it opt-out either. Actually the process killing is already opt-out afaik |
As in the old issue: PR is welcome ;) |
so 1)? Kill all children started by FAKE? |
Yes I don't see how that is worse than killing everything (ie what we already do) |
Hi,
i am not sure if this is needed/wanted but i wanted to try it out ;).
When the user presses ctrl+c, all running tasks are cancelled and all already started processes are killed. Afterwards the finalTargets are run, unless the user presses ctrl+c again to forcefully cancel.
I am also not sure if this is the right approach, but maybe you can have a look.
Some of the changes are just so that i can
#load
the target-module and test stuff.