Я модифицировал функцию из книги, которую читал: эта функция отправляет писателю «пинг» через заданный интервал. Программа выполняется в режиме go, поэтому я добавил контекст, чтобы функция могла вернуться после истечения крайнего срока контекста. Однако функция не вернется, если я не установлю начальный интервал равным 4 секундам или меньше.
package pinge
import (
"context"
"fmt"
"io"
"time"
)
const defaultInterval = time.Second * 15
func Pinger(ctx context.Context, w io.Writer, durChan <-chan time.Duration) (count int, err error) {
interval := defaultInterval
count = 0
select {
case <-ctx.Done():
return count, ctx.Err()
case interval = <-durChan:
if interval <= 0 {
interval = defaultInterval
}
default:
}
t := time.NewTimer(interval)
defer func() {
if !t.Stop() {
<-t.C
}
}()
for {
select {
case <-ctx.Done():
fmt.Println("Deadline exceeded")
return count, ctx.Err()
case newInterval := <-durChan:
if newInterval > 0 {
interval = newInterval
}
if !t.Stop() {
<-t.C
}
case <-t.C:
if _, err := w.Write([]byte("ping")); err != nil {
return count, err
}
count++
}
t.Reset(interval)
}
}
Я написал тестовый сценарий для проверки этой функции, но время ожидания продолжает истекать, поскольку тест не вернулся в течение 30-секундного периода ожидания. Вот моя тестовая функция
func TestPinger(t *testing.T) {
ddl := time.Now().Add(time.Second * 10)
initInterval := time.Second * 2
countChan := make(chan int)
durChan := make(chan time.Duration, 1)
doneChan := make(chan struct{})
ctx, cancelCtx := context.WithDeadline(context.Background(), ddl)
defer cancelCtx()
r, w := io.Pipe()
durChan <- initInterval
go func() {
count, err := Pinger(ctx, w, durChan)
countChan <- count
if err != nil {
doneChan <- struct{}{}
}
}()
buf := make([]byte, 1024)
n, err := r.Read(buf)
if err != nil {
t.Error("Could not read buffer: ", err)
}
fmt.Printf("Received: %q\n", buf[:n])
var pingCount int
select {
case <- doneChan:
fmt.Println("Ping Count = ", pingCount)
return
case pingCount = <-countChan:
}
fmt.Println("Ping Count = ", pingCount)
}
Причина зависания кода заключается в том, что вы читаете из канала только один раз (т. е. первую запись). В документации к трубе написано:
Операции чтения и записи в канале сопоставляются один к одному, за исключением случаев, когда для использования одной записи требуется несколько операций чтения. То есть каждая запись в PipeWriter блокируется до тех пор, пока не будет удовлетворена одна или несколько операций чтения из PipeReader, которые полностью потребляют записанные данные.
Итак, в коде функция TestPinger
будет блокироваться в режиме онлайн.
n, err := r.Read(buf)
до тех пор, пока не сработает таймер в Pinger
и w.Write([]byte("ping"))
, это приведет к возврату функции Read, а затем к блокировке оператора select
. Однако в Pinger
, когда таймер срабатывает снова, он теперь застревает в w.Write
, поскольку никто не читает из канала... поэтому цикл застревает там и, следовательно, не имеет возможности увидеть, что срок действия контекста истек. .
Исправить: Отправьте WriteCloser в Pinger и закройте w при выходе:
func Pinger(ctx context.Context, w io.WriteCloser, durChan <-chan time.Duration) (count int, err error) {
defer w.Close()
и в основной функции читайте до EOF, т.е.:
buf := bytes.NewBuffer(nil)
_, err := io.Copy(buf, r)
if err != nil {
t.Error("Could not read buffer: ", err)
}
fmt.Printf("Received: %q\n", buf.String())
Это сработало как по волшебству. Спасибо @ain