Прекращение выполнения функции при отмене контекста - PullRequest
1 голос
/ 25 мая 2020

У меня есть эта текущая функция, которая изначально не учитывала контекст.

func (s *Service) ChunkUpload(r *multipart.Reader) error {
    chunk, err := s.parseChunk(r)
    if err != nil {
        return fmt.Errorf("failed parsing chunk %w", err)
    }

    if err := os.MkdirAll(chunk.UploadDir, 02750); err != nil {
        return err
    }

    if err := s.saveChunk(chunk); err != nil {
        return fmt.Errorf("failed saving chunk %w", err)
    }

    return nil
}

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

Моя первоначальная реализация была такой.

func (s *Service) ChunkUpload(ctx context.Context, r *multipart.Reader) error {
    errCh := make(chan error)

    go func() {
        chunk, err := s.parseChunk(r)
        if err != nil {
            errCh <- fmt.Errorf("failed parsing chunk %w", err)
            return
        }

        if err := os.MkdirAll(chunk.UploadDir, 02750); err != nil {
            errCh <- err
            return
        }

        if err := s.saveChunk(chunk); err != nil {
            errCh <- fmt.Errorf("failed saving chunk %w", err)
            return
        }
    }()

    select {
    case err := <-errCh:
        return err
    case <-ctx.Done():
        return ctx.Err()
    }
}

Однако, как я думал о выполнении кода Я понял, что это не достигает моей цели. Поскольку все функции logi c находятся в отдельной подпрограмме go, даже если контекст отменяется, и я возвращаю ChunkUpload раньше, код внутри подпрограммы go будет продолжать выполняться, поэтому на самом деле не будет отличаться от исходного кода. .

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

Ответы [ 2 ]

1 голос
/ 25 мая 2020

Вызовы функций и горутины не могут быть завершены вызывающей стороной, функции и горутины должны поддерживать отмену, часто через значение context.Context или канал done.

В любом случае функции несут ответственность за проверку / мониторинг контекста, и если запрошена отмена (когда закрывается канал выполнения контекста), возвратиться раньше. Нет более простого способа / automati c.

Если задача выполняет код в al oop, удобное решение - проверять канал done на каждой итерации и возвращать, если он закрыт. Если задача является одним «монолитом», разработчик несет ответственность за использование / вставку «контрольных точек», в которых задача может быть прервана раньше, если такая отмена запрошена.

Простой способ проверить, завершен ли канал закрыто, означает использовать неблокирующий select, например:

select {
case <-ctx.Done():
    // Abort / return early
    return
default:
}

Следует проявлять осторожность, когда задача использует другие операции с каналом, поскольку они могут блокировать c недетерминированным образом. Эти выборы также должны включать канал ctx.Done():

select {
case v := <- someChannel:
    // Do something with v
case <-ctx.Done():
    // Abort / return early
    return
}

Также будьте осторожны, потому что, если полученное выше сообщение от someChannel никогда не блокируется, нет гарантии, что отмена будет обработана должным образом, потому что, если несколько сообщений могут продолжайте в select, один выбирается случайным образом (и нет никакой гарантии, что <-ctx.Done() когда-либо будет выбран). В таком случае вы можете комбинировать два вышеуказанных действия: сначала выполните неблокирующую проверку для отмены, затем используйте select с вашими операциями канала и для мониторинга отмены.

0 голосов
/ 25 мая 2020

Когда мы говорили об отмене, мы говорили о долгосрочной функции или блоке, повторяющемся несколько раз, например, http.Serve()

Что касается вашего случая, предположим, что saveChunk будет стоить секунд для выполнения, и вы хотите отменить при сохранении. Таким образом, мы можем разделить кусок на части и сохранять по одной после каждой.

for i:=0;i<n;i++{
    select {
        case err := <- s.saveChunk(chunk[i]):
        {
             if err != nil {
                  fmt.Errorf("failed saving chunk %w", err)
                  return
              }
        }
        case <-ctx.Done():
              return
    }
}
...