Dlaczego uruchomienie serwera deweloperskiego Flask uruchamia się dwukrotnie?

107

Używam Flask do tworzenia strony internetowej, a podczas opracowywania uruchamiam flask przy użyciu następującego pliku:

#!/usr/bin/env python
from datetime import datetime
from app import app
import config

if __name__ == '__main__':
    print '################### Restarting @', datetime.utcnow(), '###################'
    app.run(port=4004, debug=config.DEBUG, host='0.0.0.0')

Kiedy uruchamiam serwer lub gdy automatycznie uruchamia się ponownie, ponieważ pliki zostały zaktualizowane, zawsze wyświetla dwukrotnie wiersz wydruku:

################### Restarting @ 2014-08-26 10:51:49.167062 ###################
################### Restarting @ 2014-08-26 10:51:49.607096 ###################

Choć tak naprawdę nie stanowi to problemu (reszta działa zgodnie z oczekiwaniami), po prostu zastanawiam się, dlaczego tak się zachowuje? Jakieś pomysły?

kramer65
źródło

Odpowiedzi:

154

Narzędzie przeładowujące Werkzeug odradza proces potomny, aby mógł ponownie uruchomić ten proces za każdym razem, gdy zmieni się kod. Werkzeug to biblioteka, która dostarcza Flaskowi serwer deweloperski, kiedy dzwonisz app.run().

Zobacz restart_with_reloader()kod funkcji ; Twój skrypt jest uruchamiany ponownie z subprocess.call().

Jeśli ustawisz use_reloaderna False, zobaczysz, że zachowanie zniknie, ale wtedy również utracisz funkcję ponownego ładowania:

app.run(port=4004, debug=config.DEBUG, host='0.0.0.0', use_reloader=False)

Możesz również wyłączyć przeładowanie, używając flask runpolecenia:

FLASK_DEBUG=1 flask run --no-reload

Możesz poszukać WERKZEUG_RUN_MAINzmiennej środowiskowej, jeśli chcesz wykryć, kiedy jesteś w procesie ponownego ładowania:

import os
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
    print '################### Restarting @ {} ###################'.format(
        datetime.utcnow())

Jeśli jednak chcesz ustawić globalne modułów, powinieneś zamiast tego użyć @app.before_first_requestdekoratora w funkcji i pozwolić tej funkcji ustawić takie wartości globalne. Będzie wywoływana tylko raz po każdym przeładowaniu, gdy nadejdzie pierwsze żądanie:

@app.before_first_request
def before_first_request():
    print '########### Restarted, first request @ {} ############'.format(
        datetime.utcnow())

Należy wziąć pod uwagę, że jeśli uruchomisz to na serwerze WSGI o pełnej skali, który używa forking lub nowych podprocesów do obsługi żądań, te before_first_requestprocedury obsługi mogą być wywoływane dla każdego nowego podprocesu.

Martijn Pieters
źródło
2
Ach ok. Dziękuję za wyjaśnienie! Więc jest to uważane za normalne zachowanie? Przynajmniej dobrze, że nie ma nic złego w moim kodzie .. :)
kramer65
1
@ kramer65: jest to całkowicie normalne i oczekiwane zachowanie. :-)
Martijn Pieters
1
Czy istnieje praktyczny sposób na jednokrotne uruchomienie powolnego kodu inicjalizacyjnego, upewniając się, że jest on również wywoływany podczas pracy z wsgi (tj. Nie z app.run), ale nie czekając na pierwsze żądanie? Nie chcę, aby to pierwsze żądanie było obciążone kosztem inicjalizacji.
Kylotan
1
@Kylotan: musiałbyś sprawdzić otoczenie; jeśli ustawisz DEBUG tylko podczas uruchamiania w fazie programowania, możesz na przykład poszukać WERKZEUG_RUN_MAINzmiennej środowiskowej i uruchamiać kod tylko wtedy, gdy DEBUGma wartość false lub WERKZEUG_RUN_MAINjest ustawiony. Robi się trochę nudne.
Martijn Pieters
Żeby wyjaśnić, pomyślałem, że „funkcja przeładowywania” oznacza reaktywność (która zniweczyłaby cały cel używania dashdla mnie). Dla każdego innego noobsjak ja oznacza to tylko funkcjonalność, w której edycja / zapisanie pliku wyzwala aktualizację na żywo.
Hendy,
12

Jeśli używasz nowoczesnego flask runpolecenia, żadna z opcji nie app.runjest używana. Aby całkowicie wyłączyć przeładowanie, podaj --no-reload:

FLASK_DEBUG=1 flask run --no-reload

Ponadto, __name__ == '__main__'nigdy nie będzie prawdziwe, ponieważ aplikacja nie jest wykonywana bezpośrednio. Wykorzystaj te same pomysły, co w odpowiedzi Martijna , ale bez __main__bloku.

if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
    # do something only once, before the reloader

if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
    # do something each reload
dawidyzm
źródło
8

Miałem ten sam problem, a ja go rozwiązać poprzez ustawienie app.debugsię False. Ustawienie tego na Truepowodowało __name__ == "__main__"dwukrotne wywołanie mojego .

Carvell Wakeman
źródło
Mój __main__nadal działa dwa razy z obydwoma app.debug = Falsei app.run_server(debug=False). Czy na pewno zrobiłeś to za Ciebie, czy możesz wysłać jakiś powtarzalny kod, aby spróbować?
Hendy
Zmiana app.debug była wszystkim, co zrobiłem, aby rozwiązać go za mnie. Czy możesz potwierdzić, że main działa tylko dwa razy po uruchomieniu serwera flask? Spróbuj uruchomić minimalny działający przykład i sprawdź, czy problem występuje. Spróbuj także uruchomić minimalny przykład, który kończy się niepowodzeniem w wielu wersjach języka Python, co może być problemem. Od tego czasu przeprowadziłem migrację mojego projektu do Java i SparkJava zamiast do Pythona i Flask, więc nie pamiętam dokładnie, co rozwiązało problem.
Carvell Wakeman
Używam flaskprzez plotly dashi okazało się, że niedawno zmienili domyślny debug argument przekazany do flask. Zgaduję, że pomyliłem się powyżej i być może zrobiłem app.debug=False(co może jest zastąpione przez domyślne argumenty run_server) lub próbowałem tylko bez podania True, a nie jawnie ustawiania, jak pokazano powyżej. Teraz działa to poprawnie (upewniając się debug=False). Dzięki!
Hendy
2

Od wersji Flask 0.11 zaleca się uruchamianie aplikacji za pomocą flask runzamiast python application.py. Użycie tego ostatniego może spowodować dwukrotne uruchomienie kodu.

Jak stwierdzono tutaj :

... począwszy od Flask 0.11 zalecana jest metoda kolby. Powodem tego jest to, że ze względu na to, jak działa mechanizm przeładowania, występują dziwne efekty uboczne (takie jak dwukrotne wykonanie określonego kodu ...)

salsa_man
źródło
0

Jednym z możliwych powodów, dla których aplikacja Flask uruchamia się sama dwukrotnie, jest konfiguracja WEB_CONCURRENCYustawień w Heroku. Aby ustawić w jeden, możesz pisać w konsoli heroku config:set WEB_CONCURRENCY=1

trojek
źródło
-1

Miałem ten sam problem. Rozwiązałem to, modyfikując mój plik main i wstawiając do niego use_reloader = False. Jeśli jakaś osoba szuka tutaj obejścia tego problemu, poniższy kod pomoże Ci rozpocząć, jednak funkcja automatycznego wykrywania zmian w kodzie przez ponowne uruchomienie aplikacji nie będzie działać. Będziesz musiał ręcznie zatrzymać i ponownie uruchomić aplikację po każdej edycji w kodzie.

if __name__ == '__main__':
    app.run(debug=True, use_reloader=False)
LA
źródło