Останавливаются ли процедуры с принимающим каналом в качестве параметра, когда канал закрыт? - PullRequest
0 голосов
/ 07 декабря 2018

Я читал "Создание микросервисов с помощью go", и в книге представлен пакет apache/go-resiliency/deadline для обработки таймаутов.

deadline.go

// Package deadline implements the deadline (also known as "timeout") resiliency pattern for Go.
package deadline

import (
    "errors"
    "time"
)

// ErrTimedOut is the error returned from Run when the deadline expires.
var ErrTimedOut = errors.New("timed out waiting for function to finish")

// Deadline implements the deadline/timeout resiliency pattern.
type Deadline struct {
    timeout time.Duration
}

// New constructs a new Deadline with the given timeout.
func New(timeout time.Duration) *Deadline {
    return &Deadline{
        timeout: timeout,
    }
}

// Run runs the given function, passing it a stopper channel. If the deadline passes before
// the function finishes executing, Run returns ErrTimeOut to the caller and closes the stopper
// channel so that the work function can attempt to exit gracefully. It does not (and cannot)
// simply kill the running function, so if it doesn't respect the stopper channel then it may
// keep running after the deadline passes. If the function finishes before the deadline, then
// the return value of the function is returned from Run.
func (d *Deadline) Run(work func(<-chan struct{}) error) error {
    result := make(chan error)
    stopper := make(chan struct{})

    go func() {
        result <- work(stopper)
    }()

    select {
    case ret := <-result:
        return ret
    case <-time.After(d.timeout):
        close(stopper)
        return ErrTimedOut
    }
}

deadline_test.go

package deadline

import (
    "errors"
    "testing"
    "time"
)

func takesFiveMillis(stopper <-chan struct{}) error {
    time.Sleep(5 * time.Millisecond)
    return nil
}

func takesTwentyMillis(stopper <-chan struct{}) error {
    time.Sleep(20 * time.Millisecond)
    return nil
}

func returnsError(stopper <-chan struct{}) error {
    return errors.New("foo")
}

func TestDeadline(t *testing.T) {
    dl := New(10 * time.Millisecond)

    if err := dl.Run(takesFiveMillis); err != nil {
        t.Error(err)
    }

    if err := dl.Run(takesTwentyMillis); err != ErrTimedOut {
        t.Error(err)
    }

    if err := dl.Run(returnsError); err.Error() != "foo" {
        t.Error(err)
    }

    done := make(chan struct{})
    err := dl.Run(func(stopper <-chan struct{}) error {
        <-stopper
        close(done)
        return nil
    })
    if err != ErrTimedOut {
        t.Error(err)
    }
    <-done
}

func ExampleDeadline() {
    dl := New(1 * time.Second)

    err := dl.Run(func(stopper <-chan struct{}) error {
        // do something possibly slow
        // check stopper function and give up if timed out
        return nil
    })

    switch err {
    case ErrTimedOut:
        // execution took too long, oops
    default:
        // some other error
    }
}

1-й вопрос

// in deadline_test.go
if err := dl.Run(takesTwentyMillis); err != ErrTimedOut {
    t.Error(err)
}  

У меня проблемы с пониманием последовательности выполнения приведенного выше кода.Насколько я понимаю, потому что функция takesTwentyMillis спит дольше установленного времени ожидания 10 миллисекунд,

// in deadline.go
case <-time.After(d.timeout):
    close(stopper)
    return ErrTimedOut

time.After испускает текущее время, и этот случай выбран.Затем канал-заглушка закрывается и возвращается ErrTimeout.

Я не понимаю, , что закрывает канал-заглушку для анонимной программы, которая все еще может работать Я думаю, что когда канал-пробка закрыт, нижележащая процедура может все еще работать.

go func() {
        result <- work(stopper)
    }()

(Пожалуйста, исправьте меня, если я ошибаюсь здесь) Я думаю, что после close(stopper) эта процедура вызоветtakesTwentyMillis (= рабочая функция) со стопорным каналом в качестве параметра.И функция продолжит работу и будет спать в течение 20 миллисекунд и вернет ноль, чтобы перейти к каналу результата.И main () заканчивается здесь, верно?

Я не вижу, в чем смысл здесь закрывать канал пробки.Функция takesTwentyMillis, похоже, в любом случае не использует канал в теле функции: (.

2-й вопрос

// in deadline_test.go within TestDeadline()
done := make(chan struct{})
err := dl.Run(func(stopper <-chan struct{}) error {
    <-stopper
    close(done)
    return nil
})
if err != ErrTimedOut {
    t.Error(err)
}
<-done

Это часть, которую я не совсем понимаю. Я думаю, когдаdl.Run выполняется, канал ограничителя инициализируется, но поскольку в канале ограничителя нет значения, вызов функции будет заблокирован на <-stopper ... но поскольку я не понимаю этот код, я не понимаю, почему этокод существует в первую очередь (то есть, что этот код пытается проверить, и как он выполняется, и т. д.).


3-й (дополнительный) вопрос относительно 2-го вопроса

ИтакЯ понимаю, что когда функция Run во втором вопросе вызывает закрытие канала ограничителя, рабочая функция получает сигнал. И рабочий закрывает завершенный канал и возвращает ноль. Я использовал delve (= go debugger), чтобы увидеть это, иGDB доставляет меня к goroutine через deadline.go после строки return nil.

   err := dl.Run(func(stopper <-chan struct{}) error {
        <-stopper
        close(done)
-->     return nil   
    })

После ввода n для перехода к следующей строке delve приводит меня сюда

    go func() {
-->             result <- work(stopper)
            }()

И процесс здесь заканчивается, потому что когда я снова набираю n, командная строка запрашивает PASS и процесс завершается.Почему процесс заканчивается здесь?work(stopper), кажется, возвращает nil, который затем должен быть передан в канал результатов, верно?Но эта строка, похоже, не выполняется по какой-то причине.

Я знаю, что основная процедура, которая является функцией Run, уже вернула ErrTimedOut.Так что я думаю, это как-то связано с этим?

1 Ответ

0 голосов
/ 07 декабря 2018

1-й вопрос

Канал stopper используется для сигнализации функции, например, takesTwentyMillis, что достигнут крайний срок, и вызывающий абонент больше не заботится о его результате.Обычно это означает, что рабочая функция, такая как takesTwentyMillis, должна проверять, закрыт ли канал stopper, чтобы отменить свою работу.Тем не менее, проверка канала stopper является выбором рабочей функции.Он может или не может проверить канал.

func takesTwentyMillis(stopper <-chan struct{}) error {
    for i := 0; i < 20; i++ {
        select {
        case <-stopper:
            // caller doesn't care anymore might as well stop working
            return nil
        case <-time.After(time.Second): // simulating work
        }
    }
    // work is done
    return nil
}

2-й вопрос

Эта часть Deadline.Run() закроет канал пробки.

case <-time.After(d.timeout):
    close(stopper)

Чтение на закрытом канале (<-stopper) немедленно вернет нулевое значение для этого канала.Я думаю, что это просто проверка работающей функции, которая в конечном итоге истекает.

...