init
This commit is contained in:
commit
a13e45a324
3 changed files with 234 additions and 0 deletions
9
README.md
Normal file
9
README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
*wip
|
||||||
|
|
||||||
|
|
||||||
|
# io.pipe that times out
|
||||||
|
|
||||||
|
|
||||||
|
this is a work-in-progress.
|
||||||
|
|
||||||
|
Purpose of this is to intentionally cause r/w to close after certain time.
|
110
TimeoutPipe.go
Normal file
110
TimeoutPipe.go
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
package TimeoutPipe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var TimeoutErr error = errors.New("Pipe has timed out!")
|
||||||
|
|
||||||
|
//TimeoutPipe is a io.pipe that times out.
|
||||||
|
//Purpose of this is to stop goroutine r/w deadlocks by intentionally causing error
|
||||||
|
//less code to implement on the potentially-deadlocking-r/w goroutines, no need to worry about channels or timers.
|
||||||
|
type TimeoutPipe struct {
|
||||||
|
BufferSize int
|
||||||
|
|
||||||
|
Reader *io.PipeReader
|
||||||
|
Writer *io.PipeWriter
|
||||||
|
|
||||||
|
internalReader *io.PipeReader
|
||||||
|
internalWriter *io.PipeWriter
|
||||||
|
|
||||||
|
timer *time.Timer
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewTimeoutPipe retusn a new instance of TimeoutPipe interface.
|
||||||
|
func NewTimeoutPipe() *TimeoutPipe {
|
||||||
|
|
||||||
|
r, Wr := io.Pipe()
|
||||||
|
Re, w := io.Pipe()
|
||||||
|
t := TimeoutPipe{
|
||||||
|
BufferSize: 1500,
|
||||||
|
internalReader: r,
|
||||||
|
internalWriter: w,
|
||||||
|
Reader: Re,
|
||||||
|
Writer: Wr,
|
||||||
|
}
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
//Pipe returns instance of io.pipereader & io.pipewriter
|
||||||
|
func (t *TimeoutPipe) Pipe(timeout time.Duration) (*io.PipeReader, *io.PipeWriter) {
|
||||||
|
t.timeout = timeout
|
||||||
|
t.rwProxy()
|
||||||
|
t.start()
|
||||||
|
return t.Reader, t.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TimeoutPipe) start() {
|
||||||
|
t.timer = time.AfterFunc(t.timeout, func() {
|
||||||
|
t.closePipes(TimeoutErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TimeoutPipe) rwProxy() {
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var buffer []byte = make([]byte, t.BufferSize)
|
||||||
|
for {
|
||||||
|
var written int = 0
|
||||||
|
|
||||||
|
readLength, err := t.internalReader.Read(buffer)
|
||||||
|
if err != nil || readLength == 0 {
|
||||||
|
switch err {
|
||||||
|
case io.EOF:
|
||||||
|
t.internalWriter.Write(buffer[:readLength])
|
||||||
|
t.closePipes(err)
|
||||||
|
return
|
||||||
|
case io.ErrClosedPipe:
|
||||||
|
t.closePipes(err)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.closePipes(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.ResetTimer()
|
||||||
|
for written < readLength {
|
||||||
|
w, err := t.internalWriter.Write(buffer[written:readLength])
|
||||||
|
written += w
|
||||||
|
if err != nil {
|
||||||
|
t.closePipes(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TimeoutPipe) closePipes(e error) {
|
||||||
|
if e != nil {
|
||||||
|
t.internalReader.Close()
|
||||||
|
t.internalWriter.Close()
|
||||||
|
|
||||||
|
t.Reader.CloseWithError(e)
|
||||||
|
t.Writer.Close()
|
||||||
|
} else {
|
||||||
|
t.internalReader.Close()
|
||||||
|
t.internalWriter.Close()
|
||||||
|
|
||||||
|
t.Reader.Close()
|
||||||
|
t.Writer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
func (t *TimeoutPipe) ResetTimer() {
|
||||||
|
t.timer.Stop()
|
||||||
|
t.timer.Reset(t.timeout)
|
||||||
|
}
|
115
TimeoutPipe_test.go
Normal file
115
TimeoutPipe_test.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package TimeoutPipe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dummyData []byte = []byte("testing 12345")
|
||||||
|
var smallBuffer []byte = make([]byte, 2)
|
||||||
|
var largeBuffer []byte = make([]byte, 100)
|
||||||
|
|
||||||
|
func TestNoTimeout(t *testing.T) {
|
||||||
|
|
||||||
|
x := NewTimeoutPipe()
|
||||||
|
read, write := x.Pipe(time.Second * 5)
|
||||||
|
|
||||||
|
_, e := write.Write(dummyData)
|
||||||
|
if e != nil {
|
||||||
|
t.Error("Writer died unexpectedly!")
|
||||||
|
}
|
||||||
|
r, re := read.Read(largeBuffer)
|
||||||
|
if re != nil {
|
||||||
|
t.Error("Reader died unexpectedly!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r < len(dummyData) || r > len(dummyData) {
|
||||||
|
t.Error("unexpected write or read length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//if this function gets stuck -- something is wrong.
|
||||||
|
func TestTimeout(t *testing.T) {
|
||||||
|
x := NewTimeoutPipe()
|
||||||
|
read, _ := x.Pipe(time.Second * 1)
|
||||||
|
|
||||||
|
_, re := read.Read(largeBuffer)
|
||||||
|
if re != nil {
|
||||||
|
//this is what we want
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferedData(t *testing.T) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
x := NewTimeoutPipe()
|
||||||
|
read, write := x.Pipe(time.Second * 1)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
_, e := write.Write(dummyData)
|
||||||
|
if e != nil {
|
||||||
|
return
|
||||||
|
t.Error("Writer died unexpectedly!")
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
_, re := read.Read(smallBuffer)
|
||||||
|
if re != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteLargeData(t *testing.T) {
|
||||||
|
//we dont actually need to write large data, just set buffer size to very low ^^
|
||||||
|
x := NewTimeoutPipe()
|
||||||
|
x.BufferSize = 2
|
||||||
|
read, write := x.Pipe(time.Second * 5)
|
||||||
|
written := 0
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
_, re := read.Read(largeBuffer)
|
||||||
|
if re != nil {
|
||||||
|
break
|
||||||
|
t.Error("Reader died unexpectedly!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for written < len(dummyData) {
|
||||||
|
w, e := write.Write(dummyData)
|
||||||
|
if e != nil {
|
||||||
|
t.Error("Writer died unexpectedly!")
|
||||||
|
}
|
||||||
|
written += w
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReset(t *testing.T) {
|
||||||
|
ti := time.Now()
|
||||||
|
x := NewTimeoutPipe()
|
||||||
|
x.Pipe(time.Second * 1)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
x.ResetTimer()
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Since(ti) < 1*time.Second {
|
||||||
|
t.Error("Timer was not reset properly!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue