Описание функций пакета Context
Categories:
func AfterFunc
func AfterFunc(ctx Context, f func()) (stop func() bool)
AfterFunc организует вызов f в собственной горутине после отмены ctx. Если ctx уже отменен, AfterFunc вызывает f сразу в своей собственной горутине.
Несколько вызовов AfterFunc на одном контексте работают независимо; один не заменяет другой.
Вызов возвращаемой функции stop прекращает ассоциацию ctx с f. Он возвращает true, если вызов остановил запуск f. Если stop возвращает false, то либо контекст отменен и f была запущена в своей собственной goroutine, либо f уже была остановлена. Функция stop не ждет завершения f перед возвратом. Если вызывающей стороне необходимо знать, завершено ли выполнение f, она должна явно согласовать это с f.
Если у ctx есть метод “AfterFunc(func()) func() bool”, AfterFunc будет использовать его для планирования вызова.
Пример (Cond)
В этом примере AfterFunc используется для определения функции, которая ожидает синхронизацию с Cond, прекращая ожидание при отмене контекста.
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
waitOnCond := func(ctx context.Context, cond *sync.Cond, conditionMet func() bool) error {
// Создаем канал для отмены AfterFunc
done := make(chan struct{})
defer close(done)
// Настраиваем функцию отмены по таймауту контекста
stopf := context.AfterFunc(ctx, func() {
cond.L.Lock()
defer cond.L.Unlock()
select {
case <-done:
// Уже завершились, не нужно broadcast
return
default:
// Пробуждаем все ожидающие горутины
cond.Broadcast()
}
})
defer stopf()
// Ожидаем выполнения условия
for !conditionMet() {
// Проверяем контекст перед ожиданием
if ctx.Err() != nil {
return ctx.Err()
}
cond.Wait()
// Проверяем контекст после пробуждения
if ctx.Err() != nil {
return ctx.Err()
}
}
return nil
}
cond := sync.NewCond(&sync.Mutex{})
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < 4; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
cond.L.Lock()
defer cond.L.Unlock()
err := waitOnCond(ctx, cond, func() bool { return false })
fmt.Printf("Goroutine %d finished after %v with error: %v\n",
id, time.Since(start), err)
}(i)
}
wg.Wait()
fmt.Println("All goroutines completed")
}
Пример Connection
В этом примере используется AfterFunc для определения функции, которая считывает данные из net.Conn, останавливая считывание при отмене контекста.
package main
import (
"context"
"fmt"
"net"
"time"
)
func readFromConn(ctx context.Context, conn net.Conn, b []byte) (n int, err error) {
stopc := make(chan struct{})
defer close(stopc) // Гарантируем закрытие канала
// Устанавливаем функцию отмены чтения при отмене контекста
stop := context.AfterFunc(ctx, func() {
conn.SetReadDeadline(time.Now()) // Прерываем текущее чтение
close(stopc)
})
defer func() {
if !stop() {
// Если AfterFunc был запущен, сбрасываем дедлайн
<-stopc
conn.SetReadDeadline(time.Time{})
}
}()
n, err = conn.Read(b)
if ctx.Err() != nil {
return 0, ctx.Err() // Возвращаем ошибку контекста если он отменен
}
return n, err
}
func main() {
// Создаем тестовый сервер
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
fmt.Println("Failed to create listener:", err)
return
}
defer listener.Close()
// Устанавливаем соединение
conn, err := net.Dial(listener.Addr().Network(), listener.Addr().String())
if err != nil {
fmt.Println("Failed to dial:", err)
return
}
defer conn.Close()
// Создаем контекст с таймаутом
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
// Читаем данные
b := make([]byte, 1024)
_, err = readFromConn(ctx, conn, b)
// Проверяем тип ошибки
switch {
case err == nil:
fmt.Println("Read completed successfully")
case ctx.Err() != nil:
fmt.Println("Operation canceled:", ctx.Err())
default:
fmt.Println("Read error:", err)
}
}
Дополнительные рекомендации:
-
Таймауты соединения:
conn.SetDeadline(time.Now().Add(30 * time.Second))
-
Буферизация:
bufReader := bufio.NewReader(conn) n, err = bufReader.Read(b)
-
Повторные попытки:
for retries := 0; retries < 3; retries++ { n, err = readFromConn(ctx, conn, b) if err == nil || !isTemporary(err) { break } }
-
Логирование:
log.Printf("Read %d bytes, error: %v", n, err)
Пример Merge
В этом примере AfterFunc используется для определения функции, которая объединяет сигналы отмены двух Контекстов.
package main
import (
"context"
"errors"
"fmt"
"sync"
)
func main() {
// mergeCancel возвращает контекст, который отменяется при отмене любого из исходных контекстов
mergeCancel := func(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) {
mergedCtx, cancel := context.WithCancelCause(ctx1)
var once sync.Once
// Отслеживаем отмену первого контекста
go func() {
select {
case <-ctx1.Done():
once.Do(func() {
cancel(context.Cause(ctx1))
})
case <-ctx2.Done():
once.Do(func() {
cancel(context.Cause(ctx2))
})
case <-mergedCtx.Done():
// Уже отменен другим путем
}
}()
return mergedCtx, func() {
once.Do(func() {
cancel(context.Canceled)
})
}
}
// Создаем два контекста с возможностью отмены
ctx1, cancel1 := context.WithCancelCause(context.Background())
defer cancel1(errors.New("ctx1 canceled"))
ctx2, cancel2 := context.WithCancelCause(context.Background())
// Объединяем контексты
mergedCtx, mergedCancel := mergeCancel(ctx1, ctx2)
defer mergedCancel()
// Отменяем второй контекст
cancel2(errors.New("ctx2 canceled"))
// Ждем отмены объединенного контекста
<-mergedCtx.Done()
// Выводим причину отмены
fmt.Println("Merged context canceled because:", context.Cause(mergedCtx))
}
Ключевые моменты:
-
Потокобезопасность:
var once sync.Once once.Do(func() { cancel(context.Cause(ctx)) })
-
Полное отслеживание контекстов:
select { case <-ctx1.Done(): case <-ctx2.Done(): case <-mergedCtx.Done(): }
-
Четкая причина отмены:
fmt.Println("Merged context canceled because:", context.Cause(mergedCtx))
-
Гарантированная очистка:
defer mergedCancel()
func Cause
func Cause(c Context) error
Cause
возвращает не нулевую ошибку, объясняющую, почему c
был отменен. Первая отмена c
или одного из его родителей устанавливает причину. Если эта отмена произошла через вызов CancelCauseFunc(err)
, то Cause
возвращает err
. В противном случае Cause(c)
возвращает то же значение, что и c.Err()
. Cause
возвращает nil
, если c
еще не был отменен.
func WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel
возвращает производный контекст, который указывает на родительский контекст, но имеет новый канал Done
. Канал Done
возвращенного контекста закрывается при вызове возвращенной функции отмены или при закрытии канала Done
родительского контекста, в зависимости от того, что произойдет раньше.
Отмена этого контекста освобождает связанные с ним ресурсы, поэтому код должен вызывать отмену, как только операции, выполняемые в этом контексте, завершатся.
Пример
Этот пример демонстрирует использование отменяемого контекста для предотвращения утечки данных из горутины. К концу выполнения функции примера горутина, запущенная gen, вернется без утечки.
package main
import (
"context"
"fmt"
"sync"
)
func main() {
// gen генерирует целые числа в отдельной горутине и
// отправляет их в возвращаемый канал.
// Вызывающая сторона должна отменить контекст после
// завершения потребления чисел, чтобы избежать утечки горутины.
gen := func(ctx context.Context) <-chan int {
dst := make(chan int)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
defer close(dst) // Закрываем канал при завершении
n := 1
for {
select {
case <-ctx.Done():
return
case dst <- n:
n++
}
}
}()
// Горутина для безопасного завершения
go func() {
wg.Wait()
}()
return dst
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Отменяем контекст при завершении
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
// Дополнительная отмена контекста (хотя defer уже сделает это)
cancel()
}
func WithCancelCause
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)
WithCancelCause
ведет себя как WithCancel
, но возвращает CancelCauseFunc
вместо CancelFunc
. Вызов cancel
с no-nil
ошибкой (причина) записывает эту ошибку в ctx
; затем ее можно получить с помощью Cause(ctx)
. Вызов cancel
с nil
устанавливает причину в Canceled
.
Пример использования:
ctx, cancel := context.WithCancelCause(parent)
cancel(myError)
ctx.Err() // возвращает context.Canceled
context.Cause(ctx) // возвращает myError
func WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
WithDeadline
возвращает производный контекст, который указывает на родительский контекст, но имеет срок, скорректированный так, чтобы он не был позднее d
. Если срок родительского контекста уже раньше d
, WithDeadline(parent, d)
семантически эквивалентен parent
. Возвращаемый канал [Context.Done]
закрывается по истечении срока, при вызове возвращаемой функции cancel
или при закрытии канала Done
родительского контекста, в зависимости от того, что произойдет раньше.
Отмена этого контекста освобождает связанные с ним ресурсы, поэтому код должен вызывать отмену, как только операции, выполняемые в этом контексте, завершатся.
Пример
В этом примере контекст с произвольным сроком передается блокирующей функции, чтобы сообщить ей, что она должна прекратить свою работу, как только до нее доберется.
package main
import (
"context"
"fmt"
"time"
)
func main() {
shortDuration := 50 * time.Millisecond // Явное указание времени таймаута
d := time.Now().Add(shortDuration)
// Создаем контекст с дедлайном
ctx, cancel := context.WithDeadline(context.Background(), d)
defer cancel() // Важно вызывать cancel для освобождения ресурсов
neverReady := make(chan struct{}) // Канал, который никогда не будет готов
select {
case <-neverReady:
fmt.Println("ready") // Эта ветка никогда не выполнится
case <-ctx.Done():
// Проверяем причину завершения контекста
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context deadline exceeded")
case context.Canceled:
fmt.Println("context canceled")
default:
fmt.Println("context done for unknown reason")
}
}
}
func WithDeadlineCause
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)
WithDeadlineCause
ведет себя как WithDeadline
, но также устанавливает причину возвращаемого контекста при превышении срока. Возвращаемая CancelFunc
не устанавливает причину.
func WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout
возвращает WithDeadline(parent, time.Now().Add(timeout))
.
Отмена этого контекста освобождает связанные с ним ресурсы, поэтому код должен вызывать отмену, как только операции, выполняемые в этом контексте, завершатся:
func slowOperationWithTimeout(ctx context.Context) (Result, error) {
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel() // освобождает ресурсы, если slowOperation завершается до истечения времени ожидания
return slowOperation(ctx)
}
Пример
В этом примере контекст передается с таймаутом, чтобы сообщить блокирующей функции, что она должна завершить свою работу по истечении таймаута.
package main
import (
"context"
"fmt"
"time"
)
func main() {
shortDuration := 100 * time.Millisecond // Явное указание времени таймаута
// Создаем контекст с таймаутом
ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
defer cancel() // Всегда вызываем cancel для освобождения ресурсов
neverReady := make(chan struct{}) // Создаем канал, который никогда не закроется
select {
case <-neverReady:
fmt.Println("ready") // Эта ветка никогда не выполнится
case <-ctx.Done():
// Проверяем причину завершения контекста
switch err := ctx.Err(); err {
case context.DeadlineExceeded:
fmt.Println("context deadline exceeded")
case context.Canceled:
fmt.Println("context canceled")
default:
fmt.Printf("context done: %v\n", err)
}
}
}
func WithTimeoutCause
func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)
WithTimeoutCause
ведет себя как WithTimeout
, но также устанавливает причину возвращаемого Context
при истечении таймаута. Возвращаемый CancelFunc
не устанавливает причину.