func (p *Engine) SetSendResult(ctx context.Context, res *notification.SendResult) error {
	var err error
	permission.SudoContext(ctx, func(ctx context.Context) {
		err = p.msg.UpdateMessageStatus(ctx, res)
	})
	return err
}
SetSendResult will update the status of a message.
package engine
import (
	"context"
	"database/sql"
	"strings"
	"time"
	"github.com/pkg/errors"
	"github.com/target/goalert/alert"
	"github.com/target/goalert/app/lifecycle"
	"github.com/target/goalert/engine/cleanupmanager"
	"github.com/target/goalert/engine/escalationmanager"
	"github.com/target/goalert/engine/heartbeatmanager"
	"github.com/target/goalert/engine/message"
	"github.com/target/goalert/engine/npcyclemanager"
	"github.com/target/goalert/engine/processinglock"
	"github.com/target/goalert/engine/rotationmanager"
	"github.com/target/goalert/engine/schedulemanager"
	"github.com/target/goalert/engine/statusupdatemanager"
	"github.com/target/goalert/engine/verifymanager"
	"github.com/target/goalert/notification"
	"github.com/target/goalert/permission"
	"github.com/target/goalert/user"
	"github.com/target/goalert/util/log"
	"github.com/target/goalert/util/sqlutil"
	"go.opencensus.io/trace"
)
type updater interface {
	Name() string
	UpdateAll(context.Context) error
}
type Engine struct {
	b   *backend
	mgr *lifecycle.Manager
	shutdownCh  chan struct{}
	triggerCh   chan chan struct{}
	runLoopExit chan struct{}
	nextCycle chan chan struct{}
	modules []updater
	msg     *message.DB
	am  alert.Manager
	cfg *Config
	triggerPauseCh chan *pauseReq
}
var _ notification.ResultReceiver = &Engine{}
type pauseReq struct {
	ch  chan error
	ctx context.Context
}
func NewEngine(ctx context.Context, db *sql.DB, c *Config) (*Engine, error) {
	var err error
	p := &Engine{
		cfg:            c,
		shutdownCh:     make(chan struct{}),
		triggerCh:      make(chan chan struct{}),
		triggerPauseCh: make(chan *pauseReq),
		runLoopExit:    make(chan struct{}),
		nextCycle:      make(chan chan struct{}),
		am: c.AlertStore,
	}
	p.mgr = lifecycle.NewManager(p._run, p._shutdown)
	err = p.mgr.SetPauseResumer(lifecycle.PauseResumerFunc(
		p._pause,
		p._resume,
	))
	if err != nil {
		return nil, err
	}
	rotMgr, err := rotationmanager.NewDB(ctx, db)
	if err != nil {
		return nil, errors.Wrap(err, "rotation management backend")
	}
	schedMgr, err := schedulemanager.NewDB(ctx, db)
	if err != nil {
		return nil, errors.Wrap(err, "schedule management backend")
	}
	epMgr, err := escalationmanager.NewDB(ctx, db, c.AlertLogStore)
	if err != nil {
		return nil, errors.Wrap(err, "alert escalation backend")
	}
	ncMgr, err := npcyclemanager.NewDB(ctx, db, c.AlertLogStore)
	if err != nil {
		return nil, errors.Wrap(err, "notification cycle backend")
	}
	statMgr, err := statusupdatemanager.NewDB(ctx, db)
	if err != nil {
		return nil, errors.Wrap(err, "status update backend")
	}
	verifyMgr, err := verifymanager.NewDB(ctx, db)
	if err != nil {
		return nil, errors.Wrap(err, "verification backend")
	}
	hbMgr, err := heartbeatmanager.NewDB(ctx, db, c.AlertStore)
	if err != nil {
		return nil, errors.Wrap(err, "heartbeat processing backend")
	}
	cleanMgr, err := cleanupmanager.NewDB(ctx, db)
	if err != nil {
		return nil, errors.Wrap(err, "cleanup backend")
	}
	p.modules = []updater{
		rotMgr,
		schedMgr,
		epMgr,
		ncMgr,
		statMgr,
		verifyMgr,
		hbMgr,
		cleanMgr,
	}
	p.msg, err = message.NewDB(ctx, db, c.AlertLogStore, p.mgr)
	if err != nil {
		return nil, errors.Wrap(err, "messaging backend")
	}
	p.b, err = newBackend(db)
	if err != nil {
		return nil, errors.Wrap(err, "init backend")
	}
	return p, nil
}
func (p *Engine) WaitNextCycle(ctx context.Context) error {
	select {
	case ch := <-p.nextCycle:
		select {
		case <-ch:
			return nil
		case <-ctx.Done():
			return ctx.Err()
		}
	case <-ctx.Done():
		return ctx.Err()
	}
}
func (p *Engine) processModule(ctx context.Context, m updater) {
	defer recoverPanic(ctx, m.Name())
	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
	defer cancel()
	for {
		err := m.UpdateAll(ctx)
		if sqlErr := sqlutil.MapError(err); ctx.Err() == nil && sqlErr != nil && strings.HasPrefix(sqlErr.Code, "40") {
			continue
		}
		if err != nil && !errors.Is(err, processinglock.ErrNoLock) {
			log.Log(ctx, errors.Wrap(err, m.Name()))
		}
		break
	}
}
func (p *Engine) processMessages(ctx context.Context) {
	ctx, sp := trace.StartSpan(ctx, "Engine.MessageManager")
	defer sp.End()
	defer recoverPanic(ctx, "MessageManager")
	ctx, cancel := context.WithTimeout(ctx, time.Minute)
	defer cancel()
	err := p.msg.SendMessages(ctx, p.sendMessage, p.cfg.NotificationManager.MessageStatus)
	if errors.Is(err, processinglock.ErrNoLock) {
		return
	}
	if errors.Is(err, message.ErrAbort) {
		return
	}
	if err != nil {
		log.Log(ctx, errors.Wrap(err, "send outgoing messages"))
	}
}
func recoverPanic(ctx context.Context, name string) {
	err := recover()
	if err == nil {
		return
	}
	if e, ok := err.(error); ok {
		log.Log(ctx, errors.Wrapf(e, "PANIC in %s", name))
	} else {
		log.Log(ctx, errors.Errorf("PANIC in %s: %+v", name, err))
	}
}
func (p *Engine) Trigger() {
	<-p.triggerCh
}
func (p *Engine) TriggerAndWaitNextCycle(ctx context.Context) error {
	var waitCh chan struct{}
	select {
	case waitCh = <-p.triggerCh:  
	case waitCh = <-p.nextCycle:  
	case <-ctx.Done():
		return ctx.Err()
	}
	select {
	case <-waitCh:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}
func (p *Engine) Pause(ctx context.Context) error {
	ctx, sp := trace.StartSpan(ctx, "Engine.Pause")
	defer sp.End()
	return p.mgr.Pause(ctx)
}
func (p *Engine) _pause(ctx context.Context) error {
	ch := make(chan error, 1)
	select {
	case <-p.shutdownCh:
		return errors.New("shutting down")
	case <-ctx.Done():
		return ctx.Err()
	case p.triggerPauseCh <- &pauseReq{ch: ch, ctx: ctx}:
		select {
		case <-ctx.Done():
			defer p.Resume(ctx)
			return ctx.Err()
		case err := <-ch:
			return err
		}
	}
}
func (p *Engine) Resume(ctx context.Context) error {
	return p.mgr.Resume(ctx)
}
func (p *Engine) _resume(ctx context.Context) error {
	return nil
}
func (p *Engine) Run(ctx context.Context) error {
	return p.mgr.Run(ctx)
}
func (p *Engine) Shutdown(ctx context.Context) error {
	if p == nil {
		return nil
	}
	ctx, sp := trace.StartSpan(ctx, "Engine.Shutdown")
	defer sp.End()
	return p.mgr.Shutdown(ctx)
}
func (p *Engine) _shutdown(ctx context.Context) error {
	close(p.shutdownCh)
	<-p.runLoopExit
	return nil
}