402 lines
8.4 KiB
Go
402 lines
8.4 KiB
Go
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)
|
|
}
|