Почему при тайм-ауте функции defer не вызывается? - PullRequest
0 голосов
/ 06 ноября 2018

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

package main

import (
    "context"
    "fmt"
    "time"
)

func service1(ctx context.Context, r *Registry) {
    ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
    defer func() {
        r.Unset("service 1")
    }()
    r.Set("service 1")
    go service2(ctx, r)

    select {
    case <-ctx.Done():
        cancel()
        break
    }
 }

 func service2(ctx context.Context, r *Registry) {
    defer func() {
        r.Unset("service 2")
    }()

    r.Set("service 2")

    time.Sleep(time.Millisecond * 300)
 }

         type Registry struct {
    entries map[string]bool
 }

 func (r *Registry)Set(key string) {
    r.entries[key] = true
 }

 func (r *Registry)Unset(key string)  {
    r.entries[key] = false
 }

 func (r *Registry)Print() {
    for key, val := range r.entries  {
        fmt.Printf("%s -> %v\n", key, val)
    }
 }

 func NewRegistry() *Registry {
    r := Registry{}
    r.entries = make(map[string]bool)

    return &r
 }

func main() {
    r := NewRegistry()

    ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*200)

    go service1(ctx, r)
    // go service3(ctx, r)

    select {
    case <-ctx.Done():
        fmt.Printf("context err: %s\n", ctx.Err())
        cancel()
    }

    r.Print()
 }

В приведенном выше примере, отсрочка в service2() никогда не вызывается, и поэтому вывод:

service 1 -> false
service 2 -> true

вместо

service 1 -> false
service 2 -> false

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

И вторая часть вопроса - как изменить сервис или Registry, чтобы быть устойчивым к таким ситуациям?

1 Ответ

0 голосов
/ 06 ноября 2018

Ответ 1-й части

Скажем, у вас есть функция f1(), которая использует defer для вызова f2(), т.е. defer f2(). Дело в том, что f2 будет вызываться тогда и только тогда, когда f1 завершится, даже если произойдет паника во время выполнения. Более конкретно, посмотрите на go-defer .

Теперь наша задача - использовать отсрочку в рутине. Мы также должны помнить, что процедура go завершается, если ее родительская функция завершает работу.

Так что, если мы используем defer в функции go-рутины, то если родительская функция завершается или завершается, то функция go-рутины должна завершиться. Поскольку он выходит (не завершается), оператор defer не будет выполнен. Будет понятно, мы рисуем состояние вашей программы. enter image description here Как видите,

  • в 1-ю миллисекунду, service1() завершается раньше других. Таким образом, service2() завершается без выполнения оператора defer, а для 'service 2' не будет установлено значение false. Поскольку service1() завершается, выполняется defer, а для 'service 1' устанавливается false.
  • за 2 миллисекунды, main() завершается и программа завершается.

Итак, мы видим, как эта программа выполняется.

Ответ 2 части

Одним из возможных решений, которое я попробовал, является увеличение времени на service1() или уменьшение времени на service2().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...