Golang package for enhanced goroutine synchronization and well defined idiomatic Golang services.
- github.com/nofeaturesonlybugs/errors
bus
and listen
are considered mock packages for this example.
func fatal(e err) {
if e != nil {
fmt.Println("Error", e)
os.Exit(255)
}
}
func main() {
// Top-level Routines object.
rtns := routines.NewRoutines()
// WaitGroup-like behavior providing clean shutdown.
defer rtns.Wait()
bus := bus.NewBus()
err := bus.Start(rtns)
fatal(err)
defer bus.Stop()
listener, err := listen.New("localhost:8080")
fatal(err)
err = listener.Start(rtns)
fatal(err)
defer listener.Stop()
// Program stalls waiting on sigc or other shutdown signal...
}
The preceding example demonstrates the following well-defined behaviors:
- Calls to Start() block until the service is fully started, preventing race conditions
- Returns an error if the service can not start
- Calls to Stop() block until the service is fully stopped, preventing race conditions
The Service
interface provides the well-defined behavior of Start
and Stop
without polluting your service implementation.
type MyService struct {
Routines.Service
}
func NewMyService() *MyService {
rv := &MyService{}
rv.Service = routines.NewService(rv.start)
return rv
}
func (me *MyService) start(rtns routines.Routines) error {
listener, err := net.Listen("localhost:8080")
if err != nil {
return err
}
closed := make(chan struct{}, 1)
// Create a lambda function that handls the connection; we'll pass the
// returned function to rtns.Go() to ensure all handlers are finished
// when the service stops.
handler := func(c net.Conn) func() {
return func() {
io.Copy(c, c)
c.Close()
}
}
// Continuous loop that accepts and handles connections.
accept := func() {
for {
if conn, err := listener.Accept(); err != nil {
select {
case <-closed:
goto done
default:
// Handle error
}
} else {
rtns.Go(handler(conn))
}
}
done:
}
// When rtns.Done() signals we close the listener; this ends the accept
// function.
cleanup := func() {
<- rtns.Done()
close(closed)
listener.Close()
}
rtns.Go(cleanup)
rtns.Go(accept)
return nil
}
The preceding example is not concerned with the concurrency primitives required to implement correct Start
and Stop
behavior; it is only concerned with correctly implementing the service behavior.
All goroutines created by the service are invoked with rtns.Go()
. This ensures clean shutdown when the client calls Stop()
as Stop()
will not return until all such goroutines have completed.
Also notice that the struct
itself is not storing any variables or resources created as part of starting the service. Such resources are instantiated in the call to start()
and then remembered by the goroutines of the service. When the service is stopped all of these goroutines will end; the handles to the resources will disappear and they will be garbage collected. There is no need to remember to set such struct members to nil.