Also broke out the emergency halt process into a new function, which is also now ALWAYS called 16 minutes after sigint has been pressed, regardless if the shutdown even began in the first place.
192 lines
5.3 KiB
Go
192 lines
5.3 KiB
Go
package camfish
|
|
|
|
import "iter"
|
|
import "slices"
|
|
import "context"
|
|
|
|
// actorSets stores a sorted set of all actors, as well as information about
|
|
// their state.
|
|
type actorSets struct {
|
|
all actorSet[Actor]
|
|
inf map[Actor] actorInfo
|
|
nextOrder int
|
|
|
|
flagAdder actorSet[FlagAdder]
|
|
configProcessor actorSet[ConfigProcessor]
|
|
configurable actorSet[Configurable]
|
|
initializable actorSet[Initializable]
|
|
runnable actorSet[Runnable]
|
|
runShutdownable actorSet[RunShutdownable]
|
|
trimmable actorSet[Trimmable]
|
|
}
|
|
|
|
// actorInfo holds information about a running actor.
|
|
type actorInfo struct {
|
|
// ctx is the context of the actor. It is passed to the actor's Run
|
|
// method.
|
|
ctx context.Context
|
|
// done is used to stop the actor. It is created when the actor is added
|
|
// to the actorSets.
|
|
done func()
|
|
// stopped is closed once the actor's run method exits. If the actor
|
|
// does not have a run method, it will be nil.
|
|
stopped chan struct { }
|
|
// order is the semantic order of the actor within the sets. This is
|
|
// incremented automatically for each actor that is added. It is never
|
|
// less than one.
|
|
order int
|
|
// initial is true if this actor was added as an argument to [Run].
|
|
initial bool
|
|
}
|
|
|
|
// actorSetIface holds only the add/del/clear methods of actorSet.
|
|
type actorSetIface interface {
|
|
add(actor Actor)
|
|
del(actor Actor)
|
|
clear()
|
|
}
|
|
|
|
// All returns an iterator over all sets in the collection.
|
|
func (sets *actorSets) All() iter.Seq[actorSetIface] {
|
|
return func(yield func(actorSetIface) bool) {
|
|
yield(&sets.all)
|
|
yield(&sets.flagAdder)
|
|
yield(&sets.configProcessor)
|
|
yield(&sets.configurable)
|
|
yield(&sets.initializable)
|
|
yield(&sets.runnable)
|
|
yield(&sets.runShutdownable)
|
|
yield(&sets.trimmable)
|
|
}
|
|
}
|
|
|
|
// add adds an actor under the given parent context. This is a write operation.
|
|
func (this *actorSets) add(ctx context.Context, actor Actor) {
|
|
this.addInternal(actorInfo { }, ctx, actor)
|
|
}
|
|
|
|
// addInitial is like add, but marks the actor as initial. This is a write operation.
|
|
func (this *actorSets) addInitial(ctx context.Context, actor Actor) {
|
|
this.addInternal(actorInfo { initial: true }, ctx, actor)
|
|
}
|
|
|
|
// del removes an actor. This is a write operation.
|
|
func (this *actorSets) del(actor Actor) {
|
|
delete(this.inf, actor)
|
|
for set := range this.All() {
|
|
set.del(actor)
|
|
}
|
|
}
|
|
|
|
// clear removes all actors.
|
|
func (this *actorSets) clear() {
|
|
clear(this.inf)
|
|
for set := range this.All() {
|
|
set.clear()
|
|
}
|
|
}
|
|
|
|
// info gets information about an actor.
|
|
func (this *actorSets) info(actor Actor) actorInfo {
|
|
if this.inf == nil { return actorInfo { } }
|
|
return this.inf[actor]
|
|
}
|
|
|
|
func (this *actorSets) addInternal(inf actorInfo, ctx context.Context, actor Actor) {
|
|
if this.inf == nil { this.inf = make(map[Actor] actorInfo)}
|
|
actorCtx, done := context.WithCancel(ctx)
|
|
this.nextOrder ++
|
|
inf.ctx = actorCtx
|
|
inf.done = done
|
|
inf.order = this.nextOrder
|
|
_, isRunnable := actor.(Runnable)
|
|
_, isRunShutdownable := actor.(RunShutdownable)
|
|
if isRunnable || isRunShutdownable {
|
|
inf.stopped = make(chan struct { })
|
|
}
|
|
this.inf[actor] = inf
|
|
for set := range this.All() {
|
|
set.add(actor)
|
|
}
|
|
}
|
|
|
|
// sortActors sorts actors according to the order in which they were added.
|
|
func sortActors[T comparable] (sets *actorSets, actors []T) []T {
|
|
slices.SortFunc(actors, func (left, right T) int {
|
|
// :3
|
|
leftOrder := sets.info(any(left).(Actor)).order
|
|
rightOrder := sets.info(any(right).(Actor)).order
|
|
if leftOrder < 1 || rightOrder < 1 {
|
|
panic("could not sort actors, some were invalid")
|
|
}
|
|
switch {
|
|
case leftOrder < rightOrder: return -1
|
|
case rightOrder > leftOrder: return 1
|
|
default: return 0
|
|
}
|
|
})
|
|
return actors
|
|
}
|
|
|
|
// actorSet is a list that holds all actors of a type. it must not return any
|
|
// references to data that is referenced elsewehre, except for references to
|
|
// actors.
|
|
type actorSet[T comparable] struct {
|
|
actors map[T] string
|
|
}
|
|
|
|
// add adds an actor, if it is of the set's type
|
|
func (this *actorSet[T]) add(actor Actor) {
|
|
if this.actors == nil { this.actors = make(map[T] string) }
|
|
if item, ok := actor.(T); ok {
|
|
this.actors[item] = actor.Type()
|
|
}
|
|
}
|
|
|
|
// add removes an actor, if it is of the set's type
|
|
func (this *actorSet[T]) del(actor Actor) {
|
|
if this.actors == nil { return }
|
|
if item, ok := actor.(T); ok {
|
|
delete(this.actors, item)
|
|
}
|
|
}
|
|
|
|
// clear removes all actors.
|
|
func (this *actorSet[T]) clear() {
|
|
if this.actors == nil { return }
|
|
clear(this.actors)
|
|
}
|
|
|
|
// find returns the first actor with the given type.
|
|
func (this *actorSet[T]) find(typ string) T {
|
|
for actor, actorType := range this.actors {
|
|
if actorType == typ { return actor }
|
|
}
|
|
var zero T
|
|
return zero
|
|
}
|
|
|
|
// findAll returns all actors with the given type. This method allocates and
|
|
// returns a slice instead of an iterator for concurrency reasons.
|
|
func (this *actorSet[T]) findAll(typ string) []T {
|
|
if this.actors == nil { return nil }
|
|
slice := []T { }
|
|
for actor, actorType := range this.actors {
|
|
if actorType == typ {
|
|
slice = append(slice, actor)
|
|
}
|
|
}
|
|
return slice
|
|
}
|
|
|
|
// all returns all actors. This method allocates and returns a slice instead of
|
|
// an iterator for concurrency reasons.
|
|
func (this *actorSet[T]) all() []T {
|
|
if this.actors == nil { return nil }
|
|
slice := []T { }
|
|
for actor := range this.actors {
|
|
slice = append(slice, actor)
|
|
}
|
|
return slice
|
|
}
|