Я использую веб-фреймворк Gin для серверной службы Go API. Внутри функции-обработчика gin конечной точки API я запускаю процедуру Go для запуска некоторых фоновых задач. Предполагается, что эти задачи выполняются без какой-либо зависимости от ответа, отправленного клиенту. Но часто я сталкиваюсь с ошибкой «отмены контекста» во время операций с базой данных в дочерней подпрограмме Go, и фоновые операции завершаются неудачно.
Я не могу создать новый контекст и передать его в качестве контекста gin некоторым нужным мне данным, и есть некоторые существующие функции, которые мне нужно использовать повторно, которые ожидают *gin.context в качестве аргументов.
Я провел некоторую отладку и обнаружил, что ошибка возникает из-за того, что дочерние подпрограммы используют ту же *gin.context, что и родительская подпрограмма, то есть сам обработчик. Контекст отменяется после отправки ответа клиенту, что приводит к ошибке в операциях с БД.
Я прочитал решение, в котором говорилось, что нужно создать новый контекст с помощью context.Background() и передать его. Но мне нужно использовать сам *gin.context, иначе мне придется переписать множество функций и скопировать некоторые данные.
Еще одним многообещающим решением, которое я увидел, было предложение использовать *gin.context.Copy(). В документации джина для Copy() прямо сказано:
Copy возвращает копию текущего контекста, которую можно безопасно использовать за пределами области запроса.
Это необходимо использовать, когда контекст необходимо передать в горутину.
Я ожидал, что Copy() будет работать правильно. Но даже если я передам скопированный контекст, я получу ту же ошибку отмены контекста.
Вот что я сделал:
func SendApiResponseToClient(c *gin.Context) {
response := doSomeOperations(c)
newContext := c.Copy()
go doSomeBackGroundOperations(newContext, response)
sendResponse(response)
}
В функциях doSomeBackGroundOperations() есть некоторые операции с БД, и это приводит к сбою.
Просто чтобы убедиться, что все остальное, кроме контекста, в порядке, я добавил тайм-аут в одну секунду прямо перед отправкой ответа API клиенту, чтобы дождаться завершения фоновых операций. В этом случае ошибок не было, и все работало как положено.
Я также нашел несколько открытых проблем на GitHub в репозитории gin-gonic/gin, например:
Я считаю, что эти проблемы каким-то образом связаны с тем, с чем я столкнулся. Но я до сих пор не могу найти окончательного решения.
Любое предложение, которое может мне помочь, будет очень оценено. Спасибо

*gin.Context — это тип, который имеет поле под названием Request. Это поле Request имеет тип *http.Request и содержит стандартную библиотеку context.Context. Именно он context.Context отвечает за распространение отмены через ваш код.
Следовательно, если мы хотим изменить способ распространения этих отмен, нам нужно изменить это context.Context.
Следующий фрагмент поможет скопировать контекст Gin и прикрепить фоновый контекст:
func detachContext(c *gin.Context) *gin.Context {
// First call .Copy on the gin.Context. This doesn't behave quite as
// we'd originally expect, and references the c.Request rather than
// actually copying it. Hence, we'll need to follow up with a proper
// clone of the underlying Request.
copy := c.Copy()
// As we don't want to modify the existing http.Request,
// we want to use Clone, which handily allows us to specify
// a new context.Context.
// We use context.WithoutCancel to take a copy of the values of
// a context without inheriting it's cancellation propagation.
copy.Request = copy.Request.Clone(context.WithoutCancel(copy.Request.Context))
return copy
}
Имейте в виду, что вновь созданный контекст не имеет отмены, и это может привести к накоплению подпрограмм Go. Вы можете использовать context.WithTimeout или что-то подобное, чтобы контролировать время жизни дочерней горутины.
Наконец, учитывая все вышесказанное, я бы рекомендовал избегать *gin.Context слишком глубокого проникновения в ваш код. Это специфично для Джина. Вместо этого я бы предложил вам извлечь из него необходимую информацию на уровне обработчика HTTP, а затем передать эту информацию напрямую другим модулям кода. Это уменьшит количество, в котором весь ваш проект связан с Gin.
Большое спасибо за ваш подробный ответ. Клонирование *gin.Request.Context() без немедленной отмены — это то, что мне было нужно. Скорее всего, я буду использовать context.WithTimeout, как вы и сказали. И да, я понимаю, что это проблема, которая *gin.Context проникает чуть глубже, чем нужно. Нам нужно над этим работать.
@Исмаил рад, что смог помочь - и да, я думаю, ты обнаружишь, что если ты перестанешь позволять Джину проникать так глубоко, многие вещи станут проще. С джином все в порядке, но определенно не самый лучший, и объединение с ним такого большого количества вашего кода только причинит вам еще больше боли в будущем :)
@mkopriva Упс! Это была моя ошибка при наборе текста. Я отредактировал вопрос. В любом случае, я передаю скопированный контекст дочерней подпрограмме, и это тоже терпит неудачу. :/