Перейти обычная утечка исправить - PullRequest
0 голосов
/ 19 сентября 2018

Я сейчас работаю над небольшим сервисом.Из моего тестирования у кода, который я написал, есть возможность утечки подпрограмм go при определенных обстоятельствах, относящихся к контексту.Есть ли хороший и / или идиоматический способ исправить это?Ниже приведен пример кода.

func Handle(ctx context.Context, r *Req) (*Response, error) {
    ctx, cancel := context.WithTimeout(ctx, time.Second * 5)
    defer cancel()

    resChan := make(chan Response)
    errChan := make(chan error)

    go process(r, resChan, errChan)

    select {
    case ctx.Done():
        return nil, ctx.Err()
    case res := <-resChan:
        return &res, nil
    case err := <-errChan:
        return nil, err
    }
}

func process(r *Req, resChan chan<- Response, errChan chan<- error) {
    defer close(errChan)
    defer close(resChan)

    err := doSomeWork()
    if err != nil {
        errChan <- err
        return
    }

    err = doSomeMoreWork()
    if err != nil {
        errChan <- err
        return
    }

    res := Response{}
    resChan <- res
}

Гипотетически, если клиент отменил контекст или тайм-аут произошел до того, как у функции func была возможность отправить по одному из небуферизованных каналов (resChan, errChan)от читателя не останется ни одного читателя канала, а отправка по каналам будет блокироваться бесконечно без читателей.Поскольку в этом случае процесс не вернется, каналы также не будут закрыты.

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

func process2(ctx context.Context, r *Req, resChan chan<- Response, errChan chan<- error) {
    defer close(errChan)
    defer close(resChan)

    err := doSomeWork()
    select {
    case <-ctx.Done():
        return
    default:
        if err != nil {
            errChan <- err
            return
        }
    }

    err = doSomeMoreWork()
    select {
    case <-ctx.Done():
        return
    default:
        if err != nil {
            errChan <- err
            return
        }
    }

    res := Response{}
    select{
    case <-ctx.Done():
        return
    default:
        resChan <- res
    }
}

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

Есть ли лучший способ?Может быть, я все это неправильно.

...