Нет способа принудительно вырвать goroutine из любого кода external в эту goroutine.
Следовательно, единственный способ фактически прервать обработку - это периодически проверять, пропал ли клиент (или есть ли другой сигнал для остановки обработки).
По сути, это будет означать структурирование вашего обработчика примерно так:
func ReallyLongFunction(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
done := r.Context().Done()
// Check wheteher we're done
// do some small piece of stuff
// check whether we're done
// do another small piece of stuff
// …rinse, repeat
})
Теперь способ проверить, было ли что-то записано в канал,но без блокировки операция должна использовать идиому «выбрать по умолчанию»:
select {
case <- done:
// We're done
default:
}
Этот statemept выполняет код в блоке "// We done", если и только если записано done
для или был закрыт (что имеет место с контекстами), а в остальном выполняется пустой блок в ветви default
.
Таким образом, мы можем рефакторировать это как что-то вроде
func ReallyLongFunction(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
done := r.Context().Done()
closed := func () bool {
select {
case <- done:
return true
default:
return false
}
}
if closed() {
return
}
// do some small piece of stuff
if closed() {
return
}
// do another small piece of stuff
// …rinse, repeat
})
Остановка внешнего процесса, запущенного в обработчике HTTP
Для обращения к комментарию ОП…
Тип os/exec.Cmd
имеет поле Process
, котороеимеет тип os.Process
, и этот тип поддерживает метод Kill
, который принудительно останавливает запущенный процесс.
Единственная проблема состоит в том, что exec.Cmd.Run
блокирует, пока процесс не завершится, поэтому программа, выполняющая егоне может выполнить другой код, и если в обработчике HTTP вызывается exec.Cmd.Run
, отменить его невозможно.
Как лучше справиться с запуском программы в такой асинхронной манере, во многом зависит от того, как сам процессорганизовано, но я бы катился так:
В обработчике подготовьте процесс и затем запустите его, используя exec.Cmd.Start
(в отличие от Run
).
Проверьте значение ошибки Start
, возвращенное: если это nil
, процесс удалось запустить ОК. В противном случае каким-либо образом сообщите об ошибке клиенту и выйдите из обработчика.
Как только процесс, как известно, начался, значение exec.Cmd
содержит некоторые из его полей, заполненные информацией, связанной с процессом;Особый интерес представляет поле Process
, которое имеет тип os.Process
: этот тип имеет метод Kill
, который можно использовать для принудительного останова процесса.
Началои передайте ему значение exec.Cmd
и канал какого-либо подходящего типа (см. ниже).
Эта программа должна вызвать Wait
для нее, и как только она вернется, она должна сообщить об этом факте исходящемупереработка этого каналаактивность процесса.
После отправки данных эта программа завершается.
Основная программа (выполняющая обработчик) должна просто вызвать exec.Cmd.Process.Kill
, когда обнаружитобработчик должен завершиться.
При уничтожении процесса в конечном итоге разблокируется процедура, выполняющая Wait
для того же значения exec.Cmd
, когда завершается процесс.
После уничтожения процесса, программа-обработчик ожидает на канале ответа от программы, наблюдающего за процессом. Обработчик что-то делает с этими данными (может быть, регистрирует их или что-то еще) и завершает работу.