1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-11-09 00:14:57 +00:00
arikawa/utils/wsutil/heart.go
diamondburned 16c1658163 {,Voice}Gateway: Refactored Closing
This commit refactors both wsutil, the normal Gateway and the Voice
Gateway to have better closing behavior, which should assume less and
cover edge cases completely.
2020-10-30 11:02:37 -07:00

123 lines
2.7 KiB
Go

package wsutil
import (
"context"
"time"
"github.com/pkg/errors"
"github.com/diamondburned/arikawa/v2/internal/heart"
)
type errBrokenConnection struct {
underneath error
}
// Error formats the broken connection error with the message "explicit
// connection break."
func (err errBrokenConnection) Error() string {
return "explicit connection break: " + err.underneath.Error()
}
// Unwrap returns the underlying error.
func (err errBrokenConnection) Unwrap() error {
return err.underneath
}
// ErrBrokenConnection marks the given error as a broken connection error. This
// error will cause the pacemaker loop to break and return the error. The error,
// when stringified, will say "explicit connection break."
func ErrBrokenConnection(err error) error {
return errBrokenConnection{underneath: err}
}
// IsBrokenConnection returns true if the error is a broken connection error.
func IsBrokenConnection(err error) bool {
var broken *errBrokenConnection
return errors.As(err, &broken)
}
// TODO API
type EventLoopHandler interface {
EventHandler
HeartbeatCtx(context.Context) error
}
// PacemakerLoop provides an event loop with a pacemaker. A zero-value instance
// is a valid instance only when RunAsync is called first.
type PacemakerLoop struct {
heart.Pacemaker
Extras ExtraHandlers
ErrorLog func(error)
events <-chan Event
handler func(*OP) error
}
func (p *PacemakerLoop) errorLog(err error) {
if p.ErrorLog == nil {
WSDebug("Uncaught error:", err)
return
}
p.ErrorLog(err)
}
// Pace calls the pacemaker's Pace function.
func (p *PacemakerLoop) Pace(ctx context.Context) error {
return p.Pacemaker.PaceCtx(ctx)
}
func (p *PacemakerLoop) RunAsync(
heartrate time.Duration, evs <-chan Event, evl EventLoopHandler, exit func(error)) {
WSDebug("Starting the pacemaker loop.")
p.Pacemaker = heart.NewPacemaker(heartrate, evl.HeartbeatCtx)
p.handler = evl.HandleOP
p.events = evs
go func() { exit(p.startLoop()) }()
}
func (p *PacemakerLoop) startLoop() error {
defer WSDebug("Pacemaker loop has exited.")
defer p.Pacemaker.StopTicker()
for {
select {
case <-p.Pacemaker.Ticks:
if err := p.Pacemaker.Pace(); err != nil {
return errors.Wrap(err, "pace failed, reconnecting")
}
case ev, ok := <-p.events:
if !ok {
WSDebug("Events channel closed, stopping pacemaker.")
return nil
}
if ev.Error != nil {
return errors.Wrap(ev.Error, "event returned error")
}
o, err := DecodeOP(ev)
if err != nil {
return errors.Wrap(err, "failed to decode OP")
}
// Check the events before handling.
p.Extras.Check(o)
// Handle the event
if err := p.handler(o); err != nil {
if IsBrokenConnection(err) {
return errors.Wrap(err, "handler failed")
}
p.errorLog(err)
}
}
}
}