gosocketio/vendor/github.com/googollee/go-socket.io/engineio/payload/payload.go

402 lines
8.4 KiB
Go
Raw Permalink Normal View History

2023-11-30 17:38:20 +08:00
package payload
import (
"io"
"math"
"sync"
"sync/atomic"
"time"
"github.com/googollee/go-socket.io/engineio/frame"
"github.com/googollee/go-socket.io/engineio/packet"
)
type readArg struct {
r io.Reader
supportBinary bool
}
// Payload does encode or decode to payload protocol.
type Payload struct {
close chan struct{}
closeOnce sync.Once
err atomic.Value
pauser *pauser
readerChan chan readArg
feeding int32
readError chan error
readDeadline atomic.Value
decoder decoder
writerChan chan io.Writer
flushing int32
writeError chan error
writeDeadline atomic.Value
encoder encoder
}
// New returns a new payload.
func New(supportBinary bool) *Payload {
ret := &Payload{
close: make(chan struct{}),
pauser: newPauser(),
readerChan: make(chan readArg),
readError: make(chan error),
writerChan: make(chan io.Writer),
writeError: make(chan error),
}
ret.readDeadline.Store(time.Time{})
ret.decoder.feeder = ret
ret.writeDeadline.Store(time.Time{})
ret.encoder.supportBinary = supportBinary
ret.encoder.feeder = ret
return ret
}
// FeedIn feeds in a new reader for NextReader.
// Multi-FeedIn needs be called sync.
//
// If Close called when FeedIn, it returns io.EOF.
// If have Pause-ed when FeedIn, it returns ErrPaused.
// If NextReader has timeout, it returns ErrTimeout.
// If read error while FeedIn, it returns read error.
func (p *Payload) FeedIn(r io.Reader, supportBinary bool) error {
select {
case <-p.close:
return p.load()
default:
}
if !atomic.CompareAndSwapInt32(&p.feeding, 0, 1) {
return newOpError("read", errOverlap)
}
defer atomic.StoreInt32(&p.feeding, 0)
if ok := p.pauser.Working(); !ok {
return newOpError("payload", errPaused)
}
defer p.pauser.Done()
for {
after, ok := p.readTimeout()
if !ok {
return p.Store("read", errTimeout)
}
select {
case <-p.close:
return p.load()
case <-after:
// it may changed during wait, need check again
continue
case p.readerChan <- readArg{
r: r,
supportBinary: supportBinary,
}:
}
break
}
for {
after, ok := p.readTimeout()
if !ok {
return p.Store("read", errTimeout)
}
select {
case <-after:
// it may changed during wait, need check again
continue
case err := <-p.readError:
return p.Store("read", err)
}
}
}
// FlushOut write data from NextWriter.
// FlushOut needs be called sync.
//
// If Close called when Flushout, it return io.EOF.
// If Pause called when Flushout, it flushs out a NOOP message and return
// nil.
// If NextWriter has timeout, it returns ErrTimeout.
// If write error while FlushOut, it returns write error.
func (p *Payload) FlushOut(w io.Writer) error {
select {
case <-p.close:
return p.load()
default:
}
if !atomic.CompareAndSwapInt32(&p.flushing, 0, 1) {
return newOpError("write", errOverlap)
}
defer atomic.StoreInt32(&p.flushing, 0)
if ok := p.pauser.Working(); !ok {
_, err := w.Write(p.encoder.NOOP())
return err
}
defer p.pauser.Done()
for {
after, ok := p.writeTimeout()
if !ok {
return p.Store("write", errTimeout)
}
select {
case <-p.close:
return p.load()
case <-after:
continue
case <-p.pauser.PausingTrigger():
_, err := w.Write(p.encoder.NOOP())
return err
case p.writerChan <- w:
}
break
}
for {
after, ok := p.writeTimeout()
if !ok {
return p.Store("write", errTimeout)
}
select {
case <-after:
// it may changed during wait, need check again
case err := <-p.writeError:
return p.Store("write", err)
}
}
}
// NextReader returns a reader for next frame.
// NextReader and SetReadDeadline needs be called sync.
//
// If Close called when NextReader, it return io.EOF.
// Pause doesn't effect to NextReader. NextReader should wait till resumed
// and next FeedIn.
func (p *Payload) NextReader() (frame.Type, packet.Type, io.ReadCloser, error) {
ft, pt, r, err := p.decoder.NextReader()
return ft, pt, r, err
}
// SetReadDeadline sets next reader deadline.
// NextReader and SetReadDeadline needs be called sync.
// NextReader will wait a FeedIn call, then it returns ReadCloser which
// decodes packet from FeedIn's Reader.
//
// If Close called when SetReadDeadline, it return io.EOF.
// If beyond the time set by SetReadDeadline, it returns ErrTimeout.
// Pause doesn't effect to SetReadDeadline.
func (p *Payload) SetReadDeadline(t time.Time) error {
p.readDeadline.Store(t)
return nil
}
// NextWriter returns a writer for next frame.
// NextWriter and SetWriterDeadline needs be called sync.
// NextWriter will wait a FlushOut call, then it returns WriteCloser which
// encode package to FlushOut's Writer.
//
// If Close called when NextWriter, it returns io.EOF.
// If beyond the time set by SetWriteDeadline, it returns ErrTimeout.
// If Pause called when NextWriter, it returns ErrPaused.
func (p *Payload) NextWriter(ft frame.Type, pt packet.Type) (io.WriteCloser, error) {
return p.encoder.NextWriter(ft, pt)
}
// SetWriteDeadline sets next writer deadline.
// NextWriter and SetWriteDeadline needs be called sync.
//
// If Close called when SetWriteDeadline, it return io.EOF.
// Pause doesn't effect to SetWriteDeadline.
func (p *Payload) SetWriteDeadline(t time.Time) error {
p.writeDeadline.Store(t)
return nil
}
// Pause pauses the payload. It will wait all reader and writer closed which
// created from NextReader or NextWriter.
// It can call in multi-goroutine.
func (p *Payload) Pause() {
p.pauser.Pause()
}
// Resume resumes the payload.
// It can call in multi-goroutine.
func (p *Payload) Resume() {
p.pauser.Resume()
}
// Close closes the payload.
// It can call in multi-goroutine.
func (p *Payload) Close() error {
p.closeOnce.Do(func() {
close(p.close)
})
return nil
}
// Store stores a error in payload, and block all other request.
func (p *Payload) Store(op string, err error) error {
old := p.err.Load()
if old == nil {
if err == io.EOF || err == nil {
return err
}
op := newOpError(op, err)
p.err.Store(op)
return op
}
return old.(error)
}
func (p *Payload) readTimeout() (<-chan time.Time, bool) {
deadline := p.readDeadline.Load().(time.Time)
wait := time.Until(deadline)
if deadline.IsZero() {
// wait for every
wait = math.MaxInt64
}
if wait <= 0 {
return nil, false
}
return time.After(wait), true
}
func (p *Payload) writeTimeout() (<-chan time.Time, bool) {
deadline := p.writeDeadline.Load().(time.Time)
wait := time.Until(deadline)
if deadline.IsZero() {
// wait for every
wait = math.MaxInt64
}
if wait <= 0 {
return nil, false
}
return time.After(wait), true
}
func (p *Payload) getReader() (io.Reader, bool, error) {
select {
case <-p.close:
return nil, false, p.load()
default:
}
if ok := p.pauser.Working(); !ok {
return nil, false, newOpError("payload", errPaused)
}
p.pauser.Done()
for {
after, ok := p.readTimeout()
if !ok {
return nil, false, p.Store("read", errTimeout)
}
select {
case <-p.close:
return nil, false, p.load()
case <-p.pauser.PausedTrigger():
return nil, false, newOpError("payload", errPaused)
case <-after:
continue
case arg := <-p.readerChan:
return arg.r, arg.supportBinary, nil
}
}
}
func (p *Payload) putReader(err error) error {
select {
case <-p.close:
return p.load()
default:
}
for {
after, ok := p.readTimeout()
if !ok {
return p.Store("read", errTimeout)
}
select {
case <-p.close:
return p.load()
case <-after:
continue
case p.readError <- err:
}
return nil
}
}
func (p *Payload) getWriter() (io.Writer, error) {
select {
case <-p.close:
return nil, p.load()
default:
}
if ok := p.pauser.Working(); !ok {
return nil, newOpError("payload", errPaused)
}
p.pauser.Done()
for {
after, ok := p.writeTimeout()
if !ok {
return nil, p.Store("write", errTimeout)
}
select {
case <-p.close:
return nil, p.load()
case <-p.pauser.PausedTrigger():
return nil, newOpError("payload", errPaused)
case <-after:
continue
case w := <-p.writerChan:
return w, nil
}
}
}
func (p *Payload) putWriter(err error) error {
select {
case <-p.close:
return p.load()
default:
}
for {
after, ok := p.writeTimeout()
if !ok {
return p.Store("write", errTimeout)
}
ret := p.Store("write", err)
select {
case <-p.close:
return p.load()
case <-after:
continue
case p.writeError <- err:
return ret
}
}
}
func (p *Payload) load() error {
ret := p.err.Load()
if ret == nil {
return io.EOF
}
return ret.(error)
}