-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Avi Deitcher <avi@deitcher.net>
- Loading branch information
1 parent
127266b
commit 08a38c8
Showing
3 changed files
with
113 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package http | ||
|
||
import ( | ||
"io" | ||
"time" | ||
) | ||
|
||
// TimeoutReader reads until a preset timeout, | ||
// then returns a timeout error. | ||
// The timeout is for each read. | ||
type TimeoutReader struct { | ||
timeout time.Duration | ||
reader io.Reader | ||
} | ||
|
||
// NewTimeoutReader creates a new TimeoutReader. | ||
func NewTimeoutReader(timeout time.Duration, r io.Reader) *TimeoutReader { | ||
return &TimeoutReader{timeout, r} | ||
} | ||
|
||
// Read reads from the underlying reader. | ||
func (r *TimeoutReader) Read(p []byte) (int, error) { | ||
// channel is just used to signal when the read is done | ||
var ( | ||
n int | ||
err error | ||
) | ||
c := make(chan byte, 1) | ||
// we have to put this in a goroutine, so we do not block our main routine | ||
// waiting on it | ||
go func() { | ||
n, err = r.reader.Read(p) | ||
c <- 0 | ||
}() | ||
select { | ||
case <-c: | ||
return n, err | ||
case <-time.After(r.timeout): | ||
return 0, &ErrTimeout{} | ||
} | ||
} | ||
|
||
type ErrTimeout struct { | ||
timeout time.Duration | ||
} | ||
|
||
func (e *ErrTimeout) Error() string { | ||
return e.timeout.String() | ||
} | ||
|
||
// Is the other error the same type? We do not really care about the properties | ||
func (e *ErrTimeout) Is(err error) bool { | ||
_, ok := err.(*ErrTimeout) | ||
return ok | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package http | ||
|
||
import ( | ||
"errors" | ||
"io" | ||
"strings" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestTimeoutReaderSucceed(t *testing.T) { | ||
// Test that TimeoutReader succeeds when the underlying reader succeeds. | ||
r := NewTimeoutReader(1*time.Second, strings.NewReader("hello world")) | ||
p := make([]byte, 11) | ||
n, err := r.Read(p) | ||
if err != nil { | ||
t.Errorf("unexpected error: %v", err) | ||
} | ||
if n != 11 { | ||
t.Errorf("unexpected read size: %d", n) | ||
} | ||
if string(p) != "hello world" { | ||
t.Errorf("unexpected read: %q", string(p)) | ||
} | ||
} | ||
|
||
func TestTimeoutReaderFail(t *testing.T) { | ||
// Test that TimeoutReader fails when the underlying reader fails. | ||
r := NewTimeoutReader(1*time.Second, blockedReader{delay: 2 * time.Second, reader: strings.NewReader("hello world")}) | ||
p := make([]byte, 12) | ||
_, err := r.Read(p) | ||
if err == nil { | ||
t.Errorf("unexpected success") | ||
} | ||
if !errors.Is(err, &ErrTimeout{}) { | ||
t.Errorf("error was %v and not ErrTimeout", err) | ||
} | ||
} | ||
|
||
type blockedReader struct { | ||
delay time.Duration | ||
reader io.Reader | ||
} | ||
|
||
func (r blockedReader) Read(p []byte) (n int, err error) { | ||
time.Sleep(r.delay) | ||
return r.reader.Read(p) | ||
} |