Implemented Nickname cancellation using context, the good way

This commit is contained in:
diamondburned (Forefront) 2020-06-08 23:02:51 -07:00
parent 4aadbbc16c
commit 794342ab71
2 changed files with 47 additions and 18 deletions

View File

@ -1,6 +1,7 @@
package mock package mock
import ( import (
"context"
"math/rand" "math/rand"
"strconv" "strconv"
"strings" "strings"
@ -40,7 +41,8 @@ type Channel struct {
// up to about 12 or so. check sameAuthorLimit. // up to about 12 or so. check sameAuthorLimit.
incrAuthor uint8 incrAuthor uint8
busyWg sync.WaitGroup // single-use write-once context, written on every JoinServer
ctx context.Context
} }
var ( var (
@ -61,15 +63,25 @@ func (ch *Channel) Name() text.Rich {
return text.Rich{Content: ch.name} return text.Rich{Content: ch.name}
} }
// Nickname sets the labeler to the nickname. It simulates heavy IO. This
// function stops as cancel is called in JoinServer, as Nickname is specially
// for that.
func (ch *Channel) Nickname(labeler cchat.LabelContainer) error { func (ch *Channel) Nickname(labeler cchat.LabelContainer) error {
// Simulate IO. // Borrow the parent's context and stop fetching if the context expires.
simulateAustralianInternet() ctx, cancel := context.WithCancel(ch.ctx)
defer cancel()
// Simulate IO with cancellation. Ignore the error if it's a simulated time
// out, else return.
if err := simulateAustralianInternetCtx(ctx); err != nil && err != ErrTimedOut {
return err
}
labeler.SetLabel(ch.username) labeler.SetLabel(ch.username)
return nil return nil
} }
func (ch *Channel) JoinServer(container cchat.MessagesContainer) (func(), error) { func (ch *Channel) JoinServer(container cchat.MessagesContainer) (stop func(), err error) {
// Is this a fresh channel? If yes, generate messages with some IO latency. // Is this a fresh channel? If yes, generate messages with some IO latency.
if len(ch.messages) == 0 || ch.messageixs == nil { if len(ch.messages) == 0 || ch.messageixs == nil {
// Simulate IO. // Simulate IO.
@ -94,8 +106,8 @@ func (ch *Channel) JoinServer(container cchat.MessagesContainer) (func(), error)
} }
} }
// Initialize channels for use. // Initialize context for cancellation.
doneCh := make(chan struct{}) ch.ctx, stop = context.WithCancel(context.Background())
go func() { go func() {
ticker := time.NewTicker(4 * time.Second) ticker := time.NewTicker(4 * time.Second)
@ -126,13 +138,13 @@ func (ch *Channel) JoinServer(container cchat.MessagesContainer) (func(), error)
var old = ch.randomOldMsg() var old = ch.randomOldMsg()
ch.deleteMessage(MessageHeader{old.id, time.Now()}, container) ch.deleteMessage(MessageHeader{old.id, time.Now()}, container)
case <-doneCh: case <-ch.ctx.Done():
return return
} }
} }
}() }()
return func() { doneCh <- struct{}{} }, nil return
} }
func (ch *Channel) RawMessageContent(id string) (string, error) { func (ch *Channel) RawMessageContent(id string) (string, error) {
@ -272,8 +284,8 @@ func (ch *Channel) nextID() (id uint32) {
} }
func (ch *Channel) SendMessage(msg cchat.SendableMessage) error { func (ch *Channel) SendMessage(msg cchat.SendableMessage) error {
if simulateAustralianInternet() { if err := simulateAustralianInternet(); err != nil {
return errors.New("Failed to send message: Australian Internet unsupported.") return errors.Wrap(err, "Failed to send message")
} }
go func() { go func() {
@ -360,12 +372,29 @@ func randClamp(min, max int) int {
return rand.Intn(max-min) + min return rand.Intn(max-min) + min
} }
// ErrTimedOut is returned when the simulated IO decides to fail.
var ErrTimedOut = errors.New("Australian Internet unsupported.")
// simulate network latency // simulate network latency
func simulateAustralianInternet() (lost bool) { func simulateAustralianInternet() error {
return simulateAustralianInternetCtx(context.Background())
}
func simulateAustralianInternetCtx(ctx context.Context) (err error) {
var ms = randClamp(internetMinLatency, internetMaxLatency) var ms = randClamp(internetMinLatency, internetMaxLatency)
<-time.After(time.Duration(ms) * time.Millisecond)
select {
case <-time.After(time.Duration(ms) * time.Millisecond):
// noop
case <-ctx.Done():
return ctx.Err()
}
// because australia, drop packet 20% of the time if internetCanFail is // because australia, drop packet 20% of the time if internetCanFail is
// true. // true.
return internetCanFail && rand.Intn(100) < 20 if internetCanFail && rand.Intn(100) < 20 {
return ErrTimedOut
}
return nil
} }

View File

@ -3,13 +3,13 @@ package mock
import ( import (
"encoding/json" "encoding/json"
"errors"
"strconv" "strconv"
"time" "time"
"github.com/diamondburned/cchat" "github.com/diamondburned/cchat"
"github.com/diamondburned/cchat/services" "github.com/diamondburned/cchat/services"
"github.com/diamondburned/cchat/text" "github.com/diamondburned/cchat/text"
"github.com/pkg/errors"
) )
func init() { func init() {
@ -32,8 +32,8 @@ func (s Service) Name() text.Rich {
} }
func (s Service) RestoreSession(storage map[string]string) (cchat.Session, error) { func (s Service) RestoreSession(storage map[string]string) (cchat.Session, error) {
if simulateAustralianInternet() { if err := simulateAustralianInternet(); err != nil {
return nil, errors.New("Restore failed: server machine broke") return nil, errors.Wrap(err, "Restore failed")
} }
username, ok := storage["username"] username, ok := storage["username"]
@ -70,8 +70,8 @@ func (Authenticator) AuthenticateForm() []cchat.AuthenticateEntry {
func (Authenticator) Authenticate(form []string) (cchat.Session, error) { func (Authenticator) Authenticate(form []string) (cchat.Session, error) {
// SLOW IO TIME. // SLOW IO TIME.
if simulateAustralianInternet() { if err := simulateAustralianInternet(); err != nil {
return nil, errors.New("Authentication timed out.") return nil, errors.Wrap(err, "Authentication failed")
} }
return newSession(form[0]), nil return newSession(form[0]), nil