github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/engine/events.go (about)

     1  // Copyright 2016-2022, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package engine
    16  
    17  import (
    18  	"bytes"
    19  	"time"
    20  
    21  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
    22  	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
    23  	"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
    24  	"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
    25  	"github.com/pulumi/pulumi/sdk/v3/go/common/display"
    26  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    27  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
    28  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
    29  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    30  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    31  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/deepcopy"
    32  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
    33  )
    34  
    35  // Event represents an event generated by the engine during an operation. The underlying
    36  // type for the `Payload` field will differ depending on the value of the `Type` field
    37  type Event struct {
    38  	Type    EventType
    39  	payload interface{}
    40  }
    41  
    42  func NewEvent(typ EventType, payload interface{}) Event {
    43  	ok := false
    44  	switch typ {
    45  	case CancelEvent:
    46  		ok = payload == nil
    47  	case StdoutColorEvent:
    48  		_, ok = payload.(StdoutEventPayload)
    49  	case DiagEvent:
    50  		_, ok = payload.(DiagEventPayload)
    51  	case PreludeEvent:
    52  		_, ok = payload.(PreludeEventPayload)
    53  	case SummaryEvent:
    54  		_, ok = payload.(SummaryEventPayload)
    55  	case ResourcePreEvent:
    56  		_, ok = payload.(ResourcePreEventPayload)
    57  	case ResourceOutputsEvent:
    58  		_, ok = payload.(ResourceOutputsEventPayload)
    59  	case ResourceOperationFailed:
    60  		_, ok = payload.(ResourceOperationFailedPayload)
    61  	case PolicyViolationEvent:
    62  		_, ok = payload.(PolicyViolationEventPayload)
    63  	default:
    64  		contract.Failf("unknown event type %v", typ)
    65  	}
    66  	contract.Assertf(ok, "invalid payload of type %T for event type %v", payload, typ)
    67  	return Event{
    68  		Type:    typ,
    69  		payload: payload,
    70  	}
    71  }
    72  
    73  // EventType is the kind of event being emitted.
    74  type EventType string
    75  
    76  const (
    77  	CancelEvent             EventType = "cancel"
    78  	StdoutColorEvent        EventType = "stdoutcolor"
    79  	DiagEvent               EventType = "diag"
    80  	PreludeEvent            EventType = "prelude"
    81  	SummaryEvent            EventType = "summary"
    82  	ResourcePreEvent        EventType = "resource-pre"
    83  	ResourceOutputsEvent    EventType = "resource-outputs"
    84  	ResourceOperationFailed EventType = "resource-operationfailed"
    85  	PolicyViolationEvent    EventType = "policy-violation"
    86  )
    87  
    88  func (e Event) Payload() interface{} {
    89  	return deepcopy.Copy(e.payload)
    90  }
    91  
    92  func cancelEvent() Event {
    93  	return Event{Type: CancelEvent}
    94  }
    95  
    96  // DiagEventPayload is the payload for an event with type `diag`
    97  type DiagEventPayload struct {
    98  	URN       resource.URN
    99  	Prefix    string
   100  	Message   string
   101  	Color     colors.Colorization
   102  	Severity  diag.Severity
   103  	StreamID  int32
   104  	Ephemeral bool
   105  }
   106  
   107  // PolicyViolationEventPayload is the payload for an event with type `policy-violation`.
   108  type PolicyViolationEventPayload struct {
   109  	ResourceURN       resource.URN
   110  	Message           string
   111  	Color             colors.Colorization
   112  	PolicyName        string
   113  	PolicyPackName    string
   114  	PolicyPackVersion string
   115  	EnforcementLevel  apitype.EnforcementLevel
   116  	Prefix            string
   117  }
   118  
   119  type StdoutEventPayload struct {
   120  	Message string
   121  	Color   colors.Colorization
   122  }
   123  
   124  type PreludeEventPayload struct {
   125  	IsPreview bool              // true if this prelude is for a plan operation
   126  	Config    map[string]string // the keys and values for config. For encrypted config, the values may be blinded
   127  }
   128  
   129  type SummaryEventPayload struct {
   130  	IsPreview       bool                    // true if this summary is for a plan operation
   131  	MaybeCorrupt    bool                    // true if one or more resources may be corrupt
   132  	Duration        time.Duration           // the duration of the entire update operation (zero values for previews)
   133  	ResourceChanges display.ResourceChanges // count of changed resources, useful for reporting
   134  	PolicyPacks     map[string]string       // {policy-pack: version} for each policy pack applied
   135  }
   136  
   137  type ResourceOperationFailedPayload struct {
   138  	Metadata StepEventMetadata
   139  	Status   resource.Status
   140  	Steps    int
   141  }
   142  
   143  type ResourceOutputsEventPayload struct {
   144  	Metadata StepEventMetadata
   145  	Planning bool
   146  	Debug    bool
   147  }
   148  
   149  type ResourcePreEventPayload struct {
   150  	Metadata StepEventMetadata
   151  	Planning bool
   152  	Debug    bool
   153  }
   154  
   155  // StepEventMetadata contains the metadata associated with a step the engine is performing.
   156  type StepEventMetadata struct {
   157  	Op           display.StepOp                 // the operation performed by this step.
   158  	URN          resource.URN                   // the resource URN (for before and after).
   159  	Type         tokens.Type                    // the type affected by this step.
   160  	Old          *StepEventStateMetadata        // the state of the resource before performing this step.
   161  	New          *StepEventStateMetadata        // the state of the resource after performing this step.
   162  	Res          *StepEventStateMetadata        // the latest state for the resource that is known (worst case, old).
   163  	Keys         []resource.PropertyKey         // the keys causing replacement (only for CreateStep and ReplaceStep).
   164  	Diffs        []resource.PropertyKey         // the keys causing diffs
   165  	DetailedDiff map[string]plugin.PropertyDiff // the rich, structured diff
   166  	Logical      bool                           // true if this step represents a logical operation in the program.
   167  	Provider     string                         // the provider that performed this step.
   168  }
   169  
   170  // StepEventStateMetadata contains detailed metadata about a resource's state pertaining to a given step.
   171  type StepEventStateMetadata struct {
   172  	// State contains the raw, complete state, for this resource.
   173  	State *resource.State
   174  	// the resource's type.
   175  	Type tokens.Type
   176  	// the resource's object urn, a human-friendly, unique name for the resource.
   177  	URN resource.URN
   178  	// true if the resource is custom, managed by a plugin.
   179  	Custom bool
   180  	// true if this resource is pending deletion due to a replacement.
   181  	Delete bool
   182  	// the resource's unique ID, assigned by the resource provider (or blank if none/uncreated).
   183  	ID resource.ID
   184  	// an optional parent URN that this resource belongs to.
   185  	Parent resource.URN
   186  	// true to "protect" this resource (protected resources cannot be deleted).
   187  	Protect bool
   188  	// the resource's input properties (as specified by the program). Note: because this will cross
   189  	// over rpc boundaries it will be slightly different than the Inputs found in resource_state.
   190  	// Specifically, secrets will have been filtered out, and large values (like assets) will be
   191  	// have a simple hash-based representation.  This allows clients to display this information
   192  	// properly, without worrying about leaking sensitive data, and without having to transmit huge
   193  	// amounts of data.
   194  	Inputs resource.PropertyMap
   195  	// the resource's complete output state (as returned by the resource provider).  See "Inputs"
   196  	// for additional details about how data will be transformed before going into this map.
   197  	Outputs resource.PropertyMap
   198  	// the resource's provider reference
   199  	Provider string
   200  	// InitErrors is the set of errors encountered in the process of initializing resource (i.e.,
   201  	// during create or update).
   202  	InitErrors []string
   203  }
   204  
   205  func makeEventEmitter(events chan<- Event, update UpdateInfo) (eventEmitter, error) {
   206  	target := update.GetTarget()
   207  	var secrets []string
   208  	if target != nil && target.Config.HasSecureValue() {
   209  		for k, v := range target.Config {
   210  			if !v.Secure() {
   211  				continue
   212  			}
   213  
   214  			secureValues, err := v.SecureValues(target.Decrypter)
   215  			if err != nil {
   216  				return eventEmitter{}, DecryptError{
   217  					Key: k,
   218  					Err: err,
   219  				}
   220  			}
   221  			secrets = append(secrets, secureValues...)
   222  		}
   223  	}
   224  
   225  	logging.AddGlobalFilter(logging.CreateFilter(secrets, "[secret]"))
   226  
   227  	buffer, done := make(chan Event), make(chan bool)
   228  	go queueEvents(events, buffer, done)
   229  
   230  	return eventEmitter{
   231  		done: done,
   232  		ch:   buffer,
   233  	}, nil
   234  }
   235  
   236  func makeQueryEventEmitter(events chan<- Event) (eventEmitter, error) {
   237  	buffer, done := make(chan Event), make(chan bool)
   238  
   239  	go queueEvents(events, buffer, done)
   240  
   241  	return eventEmitter{
   242  		done: done,
   243  		ch:   buffer,
   244  	}, nil
   245  }
   246  
   247  type eventEmitter struct {
   248  	done <-chan bool
   249  	ch   chan<- Event
   250  }
   251  
   252  func queueEvents(events chan<- Event, buffer chan Event, done chan bool) {
   253  	// Instead of sending to the source channel directly, buffer events to account for slow receivers.
   254  	//
   255  	// Buffering is done by a goroutine that concurrently receives from the senders and attempts to send events to the
   256  	// receiver. Events that are received while waiting for the receiver to catch up are buffered in a slice.
   257  	//
   258  	// We do not use a buffered channel because it is empirically less likely that the goroutine reading from a
   259  	// buffered channel will be scheduled when new data is placed in the channel.
   260  
   261  	defer close(done)
   262  
   263  	var queue []Event
   264  	for {
   265  		contract.Assert(buffer != nil)
   266  
   267  		e, ok := <-buffer
   268  		if !ok {
   269  			return
   270  		}
   271  		queue = append(queue, e)
   272  
   273  		// While there are events in the queue, attempt to send them to the waiting receiver. If the receiver is
   274  		// blocked and an event is received from the event senders, stick that event in the queue.
   275  		for len(queue) > 0 {
   276  			select {
   277  			case e, ok := <-buffer:
   278  				if !ok {
   279  					// If the event source has been closed, flush the queue.
   280  					for _, e := range queue {
   281  						trySendEvent(events, e)
   282  					}
   283  					return
   284  				}
   285  				queue = append(queue, e)
   286  			case events <- queue[0]:
   287  				queue = queue[1:]
   288  			}
   289  		}
   290  	}
   291  }
   292  
   293  func makeStepEventMetadata(op display.StepOp, step deploy.Step, debug bool) StepEventMetadata {
   294  	contract.Assert(op == step.Op() || step.Op() == deploy.OpRefresh)
   295  
   296  	var keys, diffs []resource.PropertyKey
   297  	if keyer, hasKeys := step.(interface{ Keys() []resource.PropertyKey }); hasKeys {
   298  		keys = keyer.Keys()
   299  	}
   300  	if differ, hasDiffs := step.(interface{ Diffs() []resource.PropertyKey }); hasDiffs {
   301  		diffs = differ.Diffs()
   302  	}
   303  
   304  	var detailedDiff map[string]plugin.PropertyDiff
   305  	if detailedDiffer, hasDetailedDiff := step.(interface {
   306  		DetailedDiff() map[string]plugin.PropertyDiff
   307  	}); hasDetailedDiff {
   308  		detailedDiff = detailedDiffer.DetailedDiff()
   309  	}
   310  
   311  	return StepEventMetadata{
   312  		Op:           op,
   313  		URN:          step.URN(),
   314  		Type:         step.Type(),
   315  		Keys:         keys,
   316  		Diffs:        diffs,
   317  		DetailedDiff: detailedDiff,
   318  		Old:          makeStepEventStateMetadata(step.Old(), debug),
   319  		New:          makeStepEventStateMetadata(step.New(), debug),
   320  		Res:          makeStepEventStateMetadata(step.Res(), debug),
   321  		Logical:      step.Logical(),
   322  		Provider:     step.Provider(),
   323  	}
   324  }
   325  
   326  func makeStepEventStateMetadata(state *resource.State, debug bool) *StepEventStateMetadata {
   327  	if state == nil {
   328  		return nil
   329  	}
   330  
   331  	return &StepEventStateMetadata{
   332  		State:      state,
   333  		Type:       state.Type,
   334  		URN:        state.URN,
   335  		Custom:     state.Custom,
   336  		Delete:     state.Delete,
   337  		ID:         state.ID,
   338  		Parent:     state.Parent,
   339  		Protect:    state.Protect,
   340  		Inputs:     filterResourceProperties(state.Inputs, debug),
   341  		Outputs:    filterResourceProperties(state.Outputs, debug),
   342  		Provider:   state.Provider,
   343  		InitErrors: state.InitErrors,
   344  	}
   345  }
   346  
   347  func (e *eventEmitter) Close() {
   348  	tryCloseEventChan(e.ch)
   349  	<-e.done
   350  }
   351  
   352  func (e *eventEmitter) sendEvent(event Event) {
   353  	trySendEvent(e.ch, event)
   354  }
   355  
   356  func (e *eventEmitter) resourceOperationFailedEvent(
   357  	step deploy.Step, status resource.Status, steps int, debug bool) {
   358  
   359  	contract.Requiref(e != nil, "e", "!= nil")
   360  
   361  	e.sendEvent(NewEvent(ResourceOperationFailed, ResourceOperationFailedPayload{
   362  		Metadata: makeStepEventMetadata(step.Op(), step, debug),
   363  		Status:   status,
   364  		Steps:    steps,
   365  	}))
   366  }
   367  
   368  func (e *eventEmitter) resourceOutputsEvent(op display.StepOp, step deploy.Step, planning bool, debug bool) {
   369  	contract.Requiref(e != nil, "e", "!= nil")
   370  
   371  	e.sendEvent(NewEvent(ResourceOutputsEvent, ResourceOutputsEventPayload{
   372  		Metadata: makeStepEventMetadata(op, step, debug),
   373  		Planning: planning,
   374  		Debug:    debug,
   375  	}))
   376  }
   377  
   378  func (e *eventEmitter) resourcePreEvent(
   379  	step deploy.Step, planning bool, debug bool) {
   380  
   381  	contract.Requiref(e != nil, "e", "!= nil")
   382  
   383  	e.sendEvent(NewEvent(ResourcePreEvent, ResourcePreEventPayload{
   384  		Metadata: makeStepEventMetadata(step.Op(), step, debug),
   385  		Planning: planning,
   386  		Debug:    debug,
   387  	}))
   388  }
   389  
   390  func (e *eventEmitter) preludeEvent(isPreview bool, cfg config.Map) {
   391  	contract.Requiref(e != nil, "e", "!= nil")
   392  
   393  	configStringMap := make(map[string]string, len(cfg))
   394  	for k, v := range cfg {
   395  		keyString := k.String()
   396  		valueString, err := v.Value(config.NewBlindingDecrypter())
   397  		contract.AssertNoError(err)
   398  		configStringMap[keyString] = valueString
   399  	}
   400  
   401  	e.sendEvent(NewEvent(PreludeEvent, PreludeEventPayload{
   402  		IsPreview: isPreview,
   403  		Config:    configStringMap,
   404  	}))
   405  }
   406  
   407  func (e *eventEmitter) summaryEvent(preview, maybeCorrupt bool, duration time.Duration,
   408  	resourceChanges display.ResourceChanges, policyPacks map[string]string) {
   409  
   410  	contract.Requiref(e != nil, "e", "!= nil")
   411  
   412  	e.sendEvent(NewEvent(SummaryEvent, SummaryEventPayload{
   413  		IsPreview:       preview,
   414  		MaybeCorrupt:    maybeCorrupt,
   415  		Duration:        duration,
   416  		ResourceChanges: resourceChanges,
   417  		PolicyPacks:     policyPacks,
   418  	}))
   419  }
   420  
   421  func (e *eventEmitter) policyViolationEvent(urn resource.URN, d plugin.AnalyzeDiagnostic) {
   422  
   423  	contract.Requiref(e != nil, "e", "!= nil")
   424  
   425  	// Write prefix.
   426  	var prefix bytes.Buffer
   427  	switch d.EnforcementLevel {
   428  	case apitype.Mandatory:
   429  		prefix.WriteString(colors.SpecError)
   430  	case apitype.Advisory:
   431  		prefix.WriteString(colors.SpecWarning)
   432  	default:
   433  		contract.Failf("Unrecognized diagnostic severity: %v", d)
   434  	}
   435  
   436  	prefix.WriteString(string(d.EnforcementLevel))
   437  	prefix.WriteString(": ")
   438  	prefix.WriteString(colors.Reset)
   439  
   440  	// Write the message itself.
   441  	var buffer bytes.Buffer
   442  	buffer.WriteString(colors.SpecNote)
   443  
   444  	buffer.WriteString(d.Message)
   445  
   446  	buffer.WriteString(colors.Reset)
   447  	buffer.WriteRune('\n')
   448  
   449  	e.sendEvent(NewEvent(PolicyViolationEvent, PolicyViolationEventPayload{
   450  		ResourceURN:       urn,
   451  		Message:           logging.FilterString(buffer.String()),
   452  		Color:             colors.Raw,
   453  		PolicyName:        d.PolicyName,
   454  		PolicyPackName:    d.PolicyPackName,
   455  		PolicyPackVersion: d.PolicyPackVersion,
   456  		EnforcementLevel:  d.EnforcementLevel,
   457  		Prefix:            logging.FilterString(prefix.String()),
   458  	}))
   459  }
   460  
   461  func diagEvent(e *eventEmitter, d *diag.Diag, prefix, msg string, sev diag.Severity,
   462  	ephemeral bool) {
   463  	contract.Requiref(e != nil, "e", "!= nil")
   464  
   465  	e.sendEvent(NewEvent(DiagEvent, DiagEventPayload{
   466  		URN:       d.URN,
   467  		Prefix:    logging.FilterString(prefix),
   468  		Message:   logging.FilterString(msg),
   469  		Color:     colors.Raw,
   470  		Severity:  sev,
   471  		StreamID:  d.StreamID,
   472  		Ephemeral: ephemeral,
   473  	}))
   474  }
   475  
   476  func (e *eventEmitter) diagDebugEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
   477  	diagEvent(e, d, prefix, msg, diag.Debug, ephemeral)
   478  }
   479  
   480  func (e *eventEmitter) diagInfoEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
   481  	diagEvent(e, d, prefix, msg, diag.Info, ephemeral)
   482  }
   483  
   484  func (e *eventEmitter) diagInfoerrEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
   485  	diagEvent(e, d, prefix, msg, diag.Infoerr, ephemeral)
   486  }
   487  
   488  func (e *eventEmitter) diagErrorEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
   489  	diagEvent(e, d, prefix, msg, diag.Error, ephemeral)
   490  }
   491  
   492  func (e *eventEmitter) diagWarningEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
   493  	diagEvent(e, d, prefix, msg, diag.Warning, ephemeral)
   494  }
   495  
   496  func filterResourceProperties(m resource.PropertyMap, debug bool) resource.PropertyMap {
   497  	return filterPropertyValue(resource.NewObjectProperty(m), debug).ObjectValue()
   498  }
   499  
   500  func filterPropertyValue(v resource.PropertyValue, debug bool) resource.PropertyValue {
   501  	switch {
   502  	case v.IsNull(), v.IsBool(), v.IsNumber():
   503  		return v
   504  	case v.IsString():
   505  		// have to ensure we filter out secrets.
   506  		return resource.NewStringProperty(logging.FilterString(v.StringValue()))
   507  	case v.IsAsset():
   508  		return resource.NewAssetProperty(filterAsset(v.AssetValue(), debug))
   509  	case v.IsArchive():
   510  		return resource.NewArchiveProperty(filterArchive(v.ArchiveValue(), debug))
   511  	case v.IsArray():
   512  		arr := make([]resource.PropertyValue, len(v.ArrayValue()))
   513  		for i, v := range v.ArrayValue() {
   514  			arr[i] = filterPropertyValue(v, debug)
   515  		}
   516  		return resource.NewArrayProperty(arr)
   517  	case v.IsObject():
   518  		obj := make(resource.PropertyMap, len(v.ObjectValue()))
   519  		for k, v := range v.ObjectValue() {
   520  			obj[k] = filterPropertyValue(v, debug)
   521  		}
   522  		return resource.NewObjectProperty(obj)
   523  	case v.IsComputed():
   524  		return resource.MakeComputed(filterPropertyValue(v.Input().Element, debug))
   525  	case v.IsOutput():
   526  		return resource.MakeComputed(filterPropertyValue(v.OutputValue().Element, debug))
   527  	case v.IsSecret():
   528  		return resource.MakeSecret(resource.NewStringProperty("[secret]"))
   529  	case v.IsResourceReference():
   530  		ref := v.ResourceReferenceValue()
   531  		return resource.NewResourceReferenceProperty(resource.ResourceReference{
   532  			URN:            resource.URN(logging.FilterString(string(ref.URN))),
   533  			ID:             filterPropertyValue(ref.ID, debug),
   534  			PackageVersion: logging.FilterString(ref.PackageVersion),
   535  		})
   536  	default:
   537  		contract.Failf("unexpected property value type %T", v.V)
   538  		return resource.PropertyValue{}
   539  	}
   540  }
   541  
   542  func filterAsset(v *resource.Asset, debug bool) *resource.Asset {
   543  	if !v.IsText() {
   544  		return v
   545  	}
   546  
   547  	// we don't want to include the full text of an asset as we serialize it over as
   548  	// events.  They represent user files and are thus are unbounded in size.  Instead,
   549  	// we only include the text if it represents a user's serialized program code, as
   550  	// that is something we want the receiver to see to display as part of
   551  	// progress/diffs/etc.
   552  	var text string
   553  	if v.IsUserProgramCode() {
   554  		// also make sure we filter this in case there are any secrets in the code.
   555  		text = logging.FilterString(resource.MassageIfUserProgramCodeAsset(v, debug).Text)
   556  	} else {
   557  		// We need to have some string here so that we preserve that this is a
   558  		// text-asset
   559  		text = "<contents elided>"
   560  	}
   561  
   562  	return &resource.Asset{
   563  		Sig:  v.Sig,
   564  		Hash: v.Hash,
   565  		Text: text,
   566  	}
   567  }
   568  
   569  func filterArchive(v *resource.Archive, debug bool) *resource.Archive {
   570  	if !v.IsAssets() {
   571  		return v
   572  	}
   573  
   574  	assets := make(map[string]interface{})
   575  	for k, v := range v.Assets {
   576  		switch v := v.(type) {
   577  		case *resource.Asset:
   578  			assets[k] = filterAsset(v, debug)
   579  		case *resource.Archive:
   580  			assets[k] = filterArchive(v, debug)
   581  		default:
   582  			contract.Failf("Unrecognized asset map type %T", v)
   583  		}
   584  	}
   585  	return &resource.Archive{
   586  		Sig:    v.Sig,
   587  		Hash:   v.Hash,
   588  		Assets: assets,
   589  	}
   590  }
   591  
   592  // Sends an event like a normal send but recovers from a panic on a
   593  // closed channel. This is generally a design smell and should be used
   594  // very sparingly and every use of this function needs to document the
   595  // need.
   596  //
   597  // eventEmitter uses tryEventSend to recover in the scenario of
   598  // cancelSource.Terminate being called (such as user pressing Ctrl+C
   599  // twice), when straggler stepExecutor workers are sending diag events
   600  // but the engine is shutting down.
   601  //
   602  // See https://github.com/pulumi/pulumi/issues/10431 for the details.
   603  func trySendEvent(ch chan<- Event, ev Event) (sent bool) {
   604  	sent = true
   605  	defer func() {
   606  		if recover() != nil {
   607  			sent = false
   608  			if logging.V(9) {
   609  				logging.V(9).Infof(
   610  					"Ignoring %v send on a closed channel %p",
   611  					ev.Type, ch)
   612  			}
   613  		}
   614  	}()
   615  	ch <- ev
   616  	return sent
   617  }
   618  
   619  // Tries to close a channel but recovers from a panic of closing a
   620  // closed channel. Restrictions on use are similarly to those of
   621  // trySendEvent.
   622  func tryCloseEventChan(ch chan<- Event) (closed bool) {
   623  	closed = true
   624  	defer func() {
   625  		if recover() != nil {
   626  			closed = false
   627  			if logging.V(9) {
   628  				logging.V(9).Infof(
   629  					"Ignoring close of a closed event channel %p",
   630  					ch)
   631  			}
   632  		}
   633  	}()
   634  	close(ch)
   635  	return closed
   636  }