Add MainRunnable interface for locking to the main thread

This commit is contained in:
Sasha Koshka 2025-11-24 10:21:41 -05:00
parent f0611e53ce
commit 32c8e7f7c3
4 changed files with 57 additions and 1 deletions

View File

@ -134,3 +134,15 @@ type RunShutdownable interface {
type Cleanupable interface {
Cleanup(ctx context.Context) error
}
// MainRunnable is any object with a function that must be bound to the main
// thread. Only one actor may implement this at a time, and it must have been
// added at the start of the environment as an argument to the run function.
type MainRunnable interface {
// RunMain is run in the main thread and must stop once ShutdownMain
// is called.
RunMain() error
// ShutdownMain is like [RunShutdownable.Shutdown], but unblocks RunMain
// instead of Run.
ShutdownMain(ctx context.Context) error
}

View File

@ -30,6 +30,7 @@ type environment struct {
name string
description string
actors usync.RWMonitor[*actorSets]
main MainRunnable
ctx context.Context
done context.CancelCauseFunc
group sync.WaitGroup

View File

@ -228,6 +228,41 @@ func (this *environment) phase60Initialization() bool {
}
func (this *environment) phase70Running() bool {
for actor := range this.All() {
if actor, ok := actor.(MainRunnable); ok {
this.main = actor
}
}
result := make(chan bool)
go func() {
result <- this.phase70RunningBody()
if this.main != nil {
shutdownCtx, done := context.WithTimeout(
context.Background(),
defaul(this.timing.shutdownTimeout.Load(),
defaultShutdownTimeout))
defer done()
this.main.ShutdownMain(shutdownCtx)
}
}()
if this.main != nil {
mainActor := this.main.(Actor)
if this.Verb() { log.Printf("(i) (70) binding %s to main thread", mainActor.Type()) }
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := this.main.RunMain()
if err != nil {
log.Printf("XXX [%s] main thread failed: %v", mainActor.Type(), err)
}
if this.Verb() { log.Printf("(i) (70) main thread exited") }
}
return <- result
}
func (this *environment) phase70RunningBody() bool {
defer this.Done(nil)
if this.Verb() { log.Println("... (70) starting up") }
this.running.Store(true)

10
run.go
View File

@ -57,7 +57,9 @@ var env environment
// goroutine. If an actor does not run for a meaningful amount of time
// after resetting/initialization before failing, it is considered erratic
// and further attempts to restart it will be spaced by a limited,
// constantly increasing time interval. The timing is configurable, but by
// constantly increasing time interval.
//
// The timing is configurable, but by
// default the threshold for a meaningful amount of runtime is 16 seconds,
// the initial delay interval is 8 seconds, the interval increase per
// attempt is 8 seconds, and the maximum interval is one hour.
@ -66,6 +68,12 @@ var env environment
// configurable, but by default it is once every minute. When an actor
// which implements [Resettable] is reset, it is given a configurable
// timeout, which is 8 minutes by default.
//
// If one of the actors directly passed to Run implements MainRunnable,
// it will be bound to the main thread and the CAMFISH environment will
// be run in a different thread. If more than one actor implementing
// MainRunnable is passed to the Run function, only the first one is
// considered.
//
// 80. Shutdown: This can be triggered by all actors being removed from the
// environment, a catastrophic error, [Done] being called, or the program