2023-05-09 11:58:20 +00:00
|
|
|
// Copyright 2016-2023, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package backend
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
2024-10-07 13:49:04 +00:00
|
|
|
"syscall"
|
2023-05-09 11:58:20 +00:00
|
|
|
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/engine"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/util/cancel"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type cancellationScope struct {
|
|
|
|
context *cancel.Context
|
|
|
|
sigint chan os.Signal
|
|
|
|
done chan bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *cancellationScope) Context() *cancel.Context {
|
|
|
|
return s.context
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *cancellationScope) Close() {
|
|
|
|
signal.Stop(s.sigint)
|
|
|
|
close(s.sigint)
|
|
|
|
<-s.done
|
|
|
|
}
|
|
|
|
|
|
|
|
type cancellationScopeSource int
|
|
|
|
|
|
|
|
var CancellationScopes = CancellationScopeSource(cancellationScopeSource(0))
|
|
|
|
|
|
|
|
func (cancellationScopeSource) NewScope(events chan<- engine.Event, isPreview bool) CancellationScope {
|
|
|
|
cancelContext, cancelSource := cancel.NewContext(context.Background())
|
|
|
|
|
|
|
|
c := &cancellationScope{
|
|
|
|
context: cancelContext,
|
2024-10-07 13:49:04 +00:00
|
|
|
// The channel for signal.Notify should be buffered https://pkg.go.dev/os/signal#Notify
|
|
|
|
sigint: make(chan os.Signal, 1),
|
|
|
|
done: make(chan bool),
|
2023-05-09 11:58:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
2024-10-07 13:49:04 +00:00
|
|
|
for sig := range c.sigint {
|
|
|
|
// If we haven't yet received a SIGINT or SIGTERM, call the cancellation func. Otherwise call the
|
|
|
|
// termination func.
|
2023-05-09 11:58:20 +00:00
|
|
|
if cancelContext.CancelErr() == nil {
|
|
|
|
message := "^C received; cancelling. If you would like to terminate immediately, press ^C again.\n"
|
2024-10-07 13:49:04 +00:00
|
|
|
if sig == syscall.SIGTERM {
|
|
|
|
message = "SIGTERM received; cancelling. If you would like to terminate immediately, send SIGTERM again.\n"
|
|
|
|
}
|
2023-05-09 11:58:20 +00:00
|
|
|
if !isPreview {
|
|
|
|
message += colors.BrightRed + "Note that terminating immediately may lead to orphaned resources " +
|
|
|
|
"and other inconsistencies.\n" + colors.Reset
|
|
|
|
}
|
Implement plugin download cancellation (#17621)
We use context.Background() when invoking plugin installation logic, and
generally do not propagate the context to all the various leaf I/O
operations performed in the course of installing plugins. As a result,
it is impossible to cancel them. Given that plugins can be 100MBs these
days, combined with the fact that we hook SIGINT and SIGTERM to block
them in attempt to provide a good user experience, this leads to
frustratingly long periods of time where you can't cancel an operation.
And, ironically, the time during which we're waiting for these plugins
to finish downloading is one of the few strictly safe points where
cancelling is fine, since generally no state-modifying operations are in
flight.
This change plumbs the true enclosing context to all of the places it
needs to go in order to make plugin installation cancellation work. Note
that I added `*WithContext` variants anytime dealing with a public
function for compatibility's sake. I will admit, I wish we could just
delete the other ones so that this is always an explicit decision.
This change also fixes a bug introduced in [a rather old pull request](
https://github.com/pulumi/pulumi/pull/5317/files), whereby we create
diagnostic events to tell the user a ^C has been registered -- but then
ignore the return values, never sending them to the engine!
I have included a test for cancellation. It is deliberately simple so it
can be deterministic (e.g., it doesn't test partial operations). I also
have not tested the myriad download sources, because it would make the
tests dependent on GitHub, GitLab, and other network sources that would
likely cause flakiness. Any/all feedback on how to improve coverage here
welcome, but it's better than nothing.
This fixes pulumi/pulumi#17594.
---------
Co-authored-by: Julien <julien@caffeine.lu>
2024-11-12 18:04:25 +00:00
|
|
|
events <- engine.NewEvent(engine.StdoutEventPayload{
|
2023-05-09 11:58:20 +00:00
|
|
|
Message: message,
|
|
|
|
Color: colors.Always,
|
|
|
|
})
|
|
|
|
|
|
|
|
cancelSource.Cancel()
|
|
|
|
} else {
|
2024-10-07 13:49:04 +00:00
|
|
|
sigdisplay := "^C"
|
|
|
|
if sig == syscall.SIGTERM {
|
|
|
|
sigdisplay = "SIGTERM"
|
|
|
|
}
|
|
|
|
message := colors.BrightRed + sigdisplay + " received; terminating" + colors.Reset
|
Implement plugin download cancellation (#17621)
We use context.Background() when invoking plugin installation logic, and
generally do not propagate the context to all the various leaf I/O
operations performed in the course of installing plugins. As a result,
it is impossible to cancel them. Given that plugins can be 100MBs these
days, combined with the fact that we hook SIGINT and SIGTERM to block
them in attempt to provide a good user experience, this leads to
frustratingly long periods of time where you can't cancel an operation.
And, ironically, the time during which we're waiting for these plugins
to finish downloading is one of the few strictly safe points where
cancelling is fine, since generally no state-modifying operations are in
flight.
This change plumbs the true enclosing context to all of the places it
needs to go in order to make plugin installation cancellation work. Note
that I added `*WithContext` variants anytime dealing with a public
function for compatibility's sake. I will admit, I wish we could just
delete the other ones so that this is always an explicit decision.
This change also fixes a bug introduced in [a rather old pull request](
https://github.com/pulumi/pulumi/pull/5317/files), whereby we create
diagnostic events to tell the user a ^C has been registered -- but then
ignore the return values, never sending them to the engine!
I have included a test for cancellation. It is deliberately simple so it
can be deterministic (e.g., it doesn't test partial operations). I also
have not tested the myriad download sources, because it would make the
tests dependent on GitHub, GitLab, and other network sources that would
likely cause flakiness. Any/all feedback on how to improve coverage here
welcome, but it's better than nothing.
This fixes pulumi/pulumi#17594.
---------
Co-authored-by: Julien <julien@caffeine.lu>
2024-11-12 18:04:25 +00:00
|
|
|
events <- engine.NewEvent(engine.StdoutEventPayload{
|
2023-05-09 11:58:20 +00:00
|
|
|
Message: message,
|
|
|
|
Color: colors.Always,
|
|
|
|
})
|
|
|
|
|
|
|
|
cancelSource.Terminate()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
close(c.done)
|
|
|
|
}()
|
2024-10-07 13:49:04 +00:00
|
|
|
signal.Notify(c.sigint, os.Interrupt, syscall.SIGTERM)
|
2023-05-09 11:58:20 +00:00
|
|
|
|
|
|
|
return c
|
|
|
|
}
|