Go, problem z contextem

0

Pisałem z tym problemem zarówno na stackoverflow, jak i na reddcie, ale raczej nie doczekam się odpowiedzi. Podejmuję ostatnią próbę tutaj, bo bardzo nie chciałbym przepisywać tego projektu na jakiś javascript, czy inny php, ale chyba będę musiał. Więc sprawa dotyczy, jak wskazuje tytuł wątku, użycia kontekstu. Chodzi o to, że chcę przerwać przetwarzanie requesta, po tym jak użytkownik odświeży lub opuści stronę. W poprzednim projekcie użyłem takiego kodu: (przy wykorzystaniu frameworku gin)

respChan := make(chan types.Result)
    defer close(respChan)

    cCp := c.Copy()

    go func() {

        for {
            fmt.Println("check")
            time.Sleep(10000 * time.Millisecond) //long task
            test := []types.ResultTXT{{Txt: "Test Resp"}}
            select {
            case <-cCp.Done():
                fmt.Println("goroutine canceled")
                return
            case respChan <- types.Result{Response: test}:
                fmt.Println("Work end")
                return
            }
        }
    }()

    select {
    case <-c.Done():
        fmt.Println("canceled")
        c.Abort()
        return
    case res := <-respChan:
        result := res.Response
        c.HTML(http.StatusOK, "response.html", gin.H{
            "Resp": result,
        })
        return

    }

I to działało (i działa) jak należy.
W nowym projekcie zastosowałem dokładnie to samo, właściwie skopiowałem po prostu ten kod, ale nie działa. Od 2 dni nie mogę dojść dlaczego. Modyfikowałem go już chyba na wszelkie możliwe sposoby, ale bez skutku. Użyłem innego frameworka (fiber) i też nic. Nie mam już pomysłu, co tutaj jest źle. Zastanawiam się, czy frontend może wpływać na poprawne działanie kontekstu? Jest to html + proste funkcje js do zmieniania zakładek i rozwijania menu + htmx do wysyłania requestów. Jeśli brakuje jakiś informacji, dostarczę wszelkie potrzebne, żeby tylko się z tym uporać. Ewentualnie, czy istniej jakiś inny sposób na monitorowanie tego, czy użytkownik dalej jest na stronie lub nie wykonał jakiejś inne akcji? Przy okazji chciałem też dopytać, czy używanie goroutyny w handlerze jest w ogóle potrzebne? Z tego co czytałem, to każdy handler jest i tak startowany w osobnej, więc nie wiem czy to ma sens?

1

Ciężko powiedzieć, bo nie znam dokładnie gin, Z drugiej strony ciężko powiedzieć, bo nie wiadomo w jaki sposób c jest cancellowane. Wklej cały kod

Przy okazji chciałem też dopytać, czy używanie goroutyny w handlerze jest w ogóle potrzebne? Z tego co czytałem, to każdy handler jest i tak startowany w osobnej, więc nie wiem czy to ma sens?

Tak, zazwyczaj wszystkie frameworki webowe tworzą nową goroutyne. Czy tworzyć nową goroutynę? Zazwyczaj nie. Oczywiście w tym przypadku może mieć to sens (trzeba więcej kodu), bo główny wątek czeka na cancellację a pomocniczy wysyła sygnały. Zauważ też, że obie goroutyny robią praktycznie to samo tj. czekaj na cancellację lub na wynik. Czy nie lepiej byłoby usunąć tą goroutynę skoro to samo możesz (i już robisz) w głównym wątku

0

Dzięki za odpowiedź, problem udało się rozwiązać. Okazuje się, że trzeba po prostu odczytać treść żądania tj. w podstawowym pakiecie http użyć r.ParseForm(). Co do pytania o tworzenie gorutyny, chodzi mi tutaj o sytuację kiedy np. wysyłam żądania do innego serwera (w tym wypadku można użyć NewRequestWithContext), przetwarzana jest jakaś ogromna tablica, itp. Pakując kod oczekujący na anulację lub wyniki do gorutyny, chciałem po prostu ją ubić kiedy użytkownik się rozłączy. Natomiast jak się nad tym zastanowiłem, nie ma to jednak sensu i lepiej chyba użyć kontekstu w takowej funkcji okresowo to sprawdzając.

slsy napisał(a):

Tak, zazwyczaj wszystkie frameworki webowe tworzą nową goroutyne. Czy tworzyć nową goroutynę? Zazwyczaj nie.

Czy nowa gorutyna jest tworzona dla każdego handleru osobno? Jeżeli tak, to kiedy właściwie jest sens ich użycia?

3
regiment napisał(a):

Natomiast jak się nad tym zastanowiłem, nie ma to jednak sensu i lepiej chyba użyć kontekstu w takowej funkcji okresowo to sprawdzając.

Tak, do takich sytuacji używa się contextu. Kontekstu nie sprawdza się okresowo, wszystkie biblioteki implementujące kontekst robią coś bardzo podobnego co ty: tworzą goroutynę i robią select sprawdzając czy przyszedł wyniki czy może kontekst został anulowany i trzeba przerwać pracę. Więc w normalnym kodzie wołasz łańcuch takich wywołań i jeśli gdzieś kontekst zostanie anulowany to zostanie zwrócony błąd, który zazwyczaj powinień przebić się do handlera.

Czy nowa gorutyna jest tworzona dla każdego handleru osobno? Jeżeli tak, to kiedy właściwie jest sens ich użycia?

To bardzo filozoficzne pytanie. Wątków (czy goroutyn) używa się głównie wtedy, gdy chcesz robić wiele rzeczy na raz np.

  • chcesz odpalić jakąś akcję w tle, która nie blokuje głównego flow np. zapis do cache
  • chcesz jednocześnie odpalić kilka requestów HTTP. Każdy trwa 1 sekundę, jak odpalisz je wszystkie na raz to wszystko zajmie 1s, jak sekwencyjnie to tyle sekund ile jest requestów
  • chcesz użyć mocy wielu rdzeniu czyli generalnie https://en.wikipedia.org/wiki/Data_parallelism
  • użycie wątków pozwala na lepsze zamodelowanie czegoś. Dobrym przykładem jest stworzenie goroutyny, która robi coś w tle oraz głównej, która czeka na wynik lub na time.After.
0
slsy napisał(a):

Tak, do takich sytuacji używa się contextu. Kontekstu nie sprawdza się okresowo, wszystkie biblioteki implementujące kontekst robią coś bardzo podobnego co ty: tworzą goroutynę i robią select sprawdzając czy przyszedł wyniki czy może kontekst został anulowany i trzeba przerwać pracę. Więc w normalnym kodzie wołasz łańcuch takich wywołań i jeśli gdzieś kontekst zostanie anulowany to zostanie zwrócony błąd, który zazwyczaj powinień przebić się do handlera.

Chodzi mi bardziej o samą implementację funkcji z użyciem kontekstem. Widziałem implementację przetwarzania jakiegoś pliku w pętli, gdzie sprawdzane jest czy kontekst nie został anulowany przy każdej iteracji, bez użycia gorutyny. Zrobienie tego tak wydaje mi się mieć sens, ponieważ automatycznie zostaje również ubita gorutyna w której funkcja została wywołana.
W każdym razie, dzięki za pomoc i rozjaśnienie kilku kwestii.

1

Hej @regiment dobrą praktyką w tej sytuacji będzie użycie middleware, gdzie można wykorzystać context.WithTimeout()

func (s *server) ContextTimeoutMiddleware(next http.HandlerFunc) http.HandlerFunc {
     retun func(w http.ResponsWriter, r *http.Request) {
       ctxWithTimeout, cancel := context.WithTimeout(r.Context(), 300)
       defer func() {
           if ctxWithTimeout.Error() != nil {
             log.Fatal("request context is exceeded")
           }
           cancel()
       }

       request := r.Clone(ctxWithTimeout)
       next(w, request)
     }
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1