Genau wie diese alte Ausgabe 1210 sagte, protokolliert Gunicorn einen Fehler, wenn der Client die Verbindung trennt, und meine Umgebung ist:
Debian GNU/Linux 7.8
nginx
Python3.4
gunicorn(19.8.1) (mit einem oder mehreren Arbeitern)
Flask-SocketIO, der Client gibt den Websocket-Transport an
Alles funktioniert gut, einschließlich Clients, außer diesem Fehlerprotokoll, zwei Cloud-unabhängigen Produktionsinstanzen, die beide dauerhaft protokollieren, aber ich kann es nicht auf meinem Entwicklungscomputer reproduzieren, der ein Mac ist.
Vielen Dank für Ihre Hilfe.
Fehlerbehandlungsanfrage /socket.io/?EIO=3&transport=websocket
Traceback (letzter Aufruf zuletzt):
Datei „/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/workers/async.py“, Zeile 56, im Handle
self.handle_request(listener_name, req, client, addr)
Datei „/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/workers/async.py“, Zeile 116, in handle_request
bzw. schließen()
Datei "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py", Zeile 409, in der Nähe
self.send_headers()
Datei „/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py“, Zeile 325, in send_headers
tosend = self.default_headers()
Datei „/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py“, Zeile 306, in default_headers
elif self.should_close():
Datei „/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py“, Zeile 229, in should_close
wenn self.status_code < 200 oder self.status_code in (204, 304):
AttributeError: 'Response'-Objekt hat kein Attribut 'status_code'
Haben Sie ein einfaches Beispiel, um es zu reproduzieren? Bitte versuchen Sie es auch mit dem neuesten Master, wenn möglich.
Zuvor habe ich es mehrmals in meiner lokalen Entwicklungsumgebung versucht, die derselbe Anwendungscode wie in der Produktionsumgebung ist, aber ich kann es nicht reproduzieren.
Und ich habe das Versionsprotokoll von Version 19.9.0 überprüft, nichts Verwandtes gefunden, ich werde es behalten
Wenn Sie sich dieses Fehlerprotokoll ansehen, wenn Sie etwas Neues finden, würde ich es hier posten.
Auch ich habe dieses Problem, insbesondere wenn ich meine gesamte Verbindung vom Client zum Websocket-Protokoll erzwinge. Meine Einstellungen sind die gleichen wie bei BoWuGit. Wenn das Polling-Protokoll vor dem Upgrade zugelassen wird, wird dies nicht angezeigt, aber ein weiterer Fehler:
`
[FEHLER] Fehlerbehandlung der Anfrage /socket.io/?EIO=3&transport=polling&t=MPRHUoV&sid=cd64be7c940e474d8728b114c3fb9bbe
Traceback (letzter Aufruf zuletzt):
Datei "/usr/local/lib/python3.6/site-packages/gunicorn/workers/async.py", Zeile 56, im Handle
self.handle_request(listener_name, req, client, addr)
Datei „/usr/local/lib/python3.6/site-packages/gunicorn/workers/async.py“, Zeile 107, in handle_request
respiter = self.wsgi(environ, resp.start_response)
Datei "/usr/local/lib/python3.6/site-packages/flask/app.py", Zeile 1994, in __call__
return self.wsgi_app(environ, start_response)
Datei "/usr/local/lib/python3.6/site-packages/flask_socketio/__init__.py", Zeile 43, in __call__
start_response)
Datei „/usr/local/lib/python3.6/site-packages/engineio/middleware.
py", Zeile 47, in __call__
return self.engineio_app.handle_request(environ, start_response)
Datei „/usr/local/lib/python3.6/site-packages/socketio/server.py“, Zeile 360, in handle_request
return self.eio.handle_request(environ, start_response)
Datei „/usr/local/lib/python3.6/site-packages/engineio/server.py“, Zeile 279, in handle_request
socket = self._get_socket(sid)
Datei „/usr/local/lib/python3.6/site-packages/engineio/server.py“, Zeile 439, in _get_socket
Raise KeyError('Sitzung wurde getrennt')
`
Aber ich vermute, dass es etwas miteinander zu tun haben könnte, da ich die Verbindung zu Websocket erzwinge, wurde dieser Fehler nicht wieder gesehen.
Dieses Problem tritt auch bei Gunicorn 19.9.0 und Flask-socketIO 3.0.2 auf, wenn Eventlet 0.24.1 verwendet wird
AttributeError: 'Response'-Objekt hat kein Attribut 'status_code'
Dieses Problem tritt auch bei den folgenden Anforderungen auf:
Flask==1.0.2
gunicorn==19.5.0
python-socketio==2.0.0
eventlet==0.24.1
Fehlermeldung beim Schließen des Webbrowsers mit offener Socket-Verbindung:
Error handling request /socket.io/?EIO=3&transport=websocket&sid=d43ec0ae0bb946debc51f1ca2e5b8a94
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/gunicorn/workers/async.py", line 52, in handle
self.handle_request(listener_name, req, client, addr)
File "/usr/lib/python2.7/dist-packages/gunicorn/workers/async.py", line 114, in handle_request
resp.close()
File "/usr/lib/python2.7/dist-packages/gunicorn/http/wsgi.py", line 403, in close
self.send_headers()
File "/usr/lib/python2.7/dist-packages/gunicorn/http/wsgi.py", line 319, in send_headers
tosend = self.default_headers()
File "/usr/lib/python2.7/dist-packages/gunicorn/http/wsgi.py", line 300, in default_headers
elif self.should_close():
File "/usr/lib/python2.7/dist-packages/gunicorn/http/wsgi.py", line 233, in should_close
if self.status_code < 200 or self.status_code in (204, 304):
AttributeError: 'Response' object has no attribute 'status_code'
Es sieht so aus, als ob dieses Problem in der neuesten Version von python-engineio behoben wurde.
Habe mit der neuesten Version von python-engineio (2.3.2) getestet, funktioniert immer noch nicht.
Irgendwelche Neuigkeiten zu diesem Thema? Ich bekomme den gleichen Fehler, wenn ich Sentry-Python verwende
Ich habe das gleiche Problem
Ereignis: 0.25.1
Kolben-Sockel: 4.2.1
gunicorn: 19.9.0
wie kann man es reproduzieren? Können Sie ein einfaches Beispiel geben?
Ich bin mir auch nicht sicher, wie ich es reproduzieren soll, aber es passiert OFT, wenn ich eine Seite in meiner Gunicorn-App aktualisiere
Tritt auf das gleiche Problem, und meine Umgebung ist die gleiche wie @eazow, während gunicorn == 20.0.4.
Es scheint, dass das Problem aufgetreten ist, nachdem ich Sentry für die Fehlerverfolgung installiert habe.
Die Ausgaben können reproduziert werden
Interessanterweise wird das Öffnen einer neuen Seite das Problem nicht hervorrufen. Nicht sicher warum. Danke!
Ich habe das gleiche Problem wie @cowbonlin . Dieselbe Gunicorn-Version auch.
Nach der Installation von Sentry erhalten wir verrückte Mengen dieses Fehlers. Obwohl es mir schwer fällt zu sagen, ob dies immer passiert ist oder nicht - da wir Fehler vor dem Wachposten nicht nachverfolgt haben.
Auch wenn es die eigentliche Funktionalität unseres Servers nicht zu beeinflussen scheint, ist dies nur eine Menge Spam.
Wir erleben das gleiche. Sentry installiert, aber deaktiviert. Irgendwelche Ideen?
Gleiches Problem mit installiertem Sentry.
Haben Sie ein Beispiel, das es ohne Wachposten reproduziert (deaktiviert oder nicht)?
Außerdem drücke ich anstelle eines Namespace manuell auf /api.
Außerdem drücke ich anstelle eines Namespace manuell auf /api.
was bedeutet das ? Ist dieser Wachposten verwandt?
Außerdem drücke ich anstelle eines Namespace manuell auf /api.
was bedeutet das ? Ist dieser Wachposten verwandt?
Nein, dies bezieht sich auf socket.io-Namespaces. Ich habe versucht, sie zu entfernen, und selbst nachdem ich sie entfernt habe, erhalte ich den Fehler. Ich erhalte jedoch diesen anderen Fehler auf dem lokalen Computer ohne Gunicorn oder Nginx, der möglicherweise damit zusammenhängt.
Das sind meine Anforderungen:
sentry_sdk == 0.14.3
Flask_SocketIO == 4.2.1
eventlet == 0.25.1
Dies ist mein Flask-Socketio-Code auf der Serverseite:
socketio = SocketIO(engineio_logger=True, logger=True, debug=True, cors_allowed_origins="*", path='/socket.io')
...
socketio.init_app(app, async_mode="eventlet")
Und das ist mein React-Socket-IO-Code auf der Client-Seite:
this.socket = io.connect(`http://localhost:5000?info=${someInfo}`, {
transports: ['websocket', 'polling'] // an attempt to keep polling as a fallback but start on websockets
});
Lassen Sie mich wissen, ob das hilft. Unter Ubuntu sieht der Fehler wie oben aus, und lokal unter Windows sieht er so aus:
```Traceback (letzter Aufruf zuletzt):
Datei „C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py“, Zeile 599, in handle_one_response
schreibe(b'')
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", Zeile 491, in write
raise AssertionError("write() before start_response()")
AssertionError: write() vor start_response()
Während der Behandlung der obigen Ausnahme ist eine weitere Ausnahme aufgetreten:
Traceback (letzter Aufruf zuletzt):
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", Zeile 357, in __init__
self.handle()
Datei „C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py“, Zeile 390, im Handle
self.handle_one_request()
Datei „C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py“, Zeile 466, in handle_one_request
self.handle_one_response()
Datei „C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py“, Zeile 609, in handle_one_response
schreiben (err_body)
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", Zeile 538, in write
wfile.flush()
Datei "C:\ProgramData\Anaconda3\lib\socket.py", Zeile 607, in write
sende self._sock.send(b) zurück
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", Zeile 397, in send
return self._send_loop(self.fd.send, Daten, Flags)
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", Zeile 384, in _send_loop
return send_method(data, *args)
ConnectionAbortedError: [WinError 10053] Eine hergestellte Verbindung wurde von der Software auf Ihrem Hostcomputer abgebrochen
Während der Behandlung der obigen Ausnahme ist eine weitere Ausnahme aufgetreten:
Traceback (letzter Aufruf zuletzt):
Datei „C:\ProgramData\Anaconda3\lib\site-packages\eventlet\hubs\hub.py“, Zeile 461, in fire_timers
Timer()
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\hubs\timer.py", Zeile 59, in __call__
cb( args, * kw)
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\semaphore.py", Zeile 147, in _do_acquire
Kellner.switch()
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenthread.py", Zeile 221, in main
result = function( args, * kwargs)
Datei „C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py“, Zeile 818, in process_request
proto.__init__(conn_state, selbst)
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", Zeile 359, in __init__
self.finish()
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", Zeile 732, fertig
BaseHTTPServer.BaseHTTPRequestHandler.finish (selbst)
Datei "C:\ProgramData\Anaconda3\lib\socketserver.py", Zeile 784, fertig
self.wfile.close()
Datei "C:\ProgramData\Anaconda3\lib\socket.py", Zeile 607, in write
sende self._sock.send(b) zurück
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", Zeile 397, in send
return self._send_loop(self.fd.send, Daten, Flags)
Datei "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", Zeile 384, in _send_loop
return send_method(data, *args)
ConnectionAbortedError: [WinError 10053] Eine hergestellte Verbindung wurde von der Software auf Ihrem Host-Rechner abgebrochen
Kann bestätigen, dass dieser Fehler verschwindet, wenn Sentry vollständig deaktiviert ist. Wäre großartig, wenn Gunicorn robust genug wäre, um damit fertig zu werden.
Bump @benoitc
Kann bestätigen, dass dieser Fehler verschwindet, wenn Sentry vollständig deaktiviert ist. Wäre großartig, wenn Gunicorn robust genug wäre, um damit fertig zu werden.
Ich habe festgestellt, dass das Deaktivieren von FlaskIntegration
des Sentry den Fehler ebenfalls verschwinden lässt.
Ähnliches Verhalten sehen. Die Verwendung von New Relic in der Produktion verursacht diesen Fehler mit Flask-Socketio. In der Entwicklung muss die Tool-Debugger-Middleware geladen werden, bevor Flask-Socketio initialisiert wird (sie wird also nicht auf die wsgi-App von engineio angewendet). Das Problem ist, dass die Produktion dort ist, wo ich wirklich nicht möchte, dass die Fehler auslösen.
Die Antwort in post_request von gunicorn config kann nicht ersetzt werden, aber ich habe versucht, einen Statuscode auf resp.status_code zu erzwingen. Es hat aber nicht gedauert.
Dieser Fehler ist reproduzierbar, wenn FlaskIntegration von Sentry zusammen mit Gunicorn und Flask-SocketIO verwendet wird. Ist eine baldige Lösung möglich?
@Canicio , wir dachten, wir sollten das versuchen, um den Fehler zu beseitigen, und selbst nach dem Deaktivieren der Integration bleibt der Fehler bestehen.
Hat jemand gemeinsam nutzbaren Code/ein minimales Beispiel für @benoitc , um davon auszugehen?
Sicher:
import sentry_sdk
from flask import Flask
from flask_socketio import SocketIO
from sentry_sdk.integrations.flask import FlaskIntegration
sentry_sdk.init(
dsn="https://[email protected]/0",
integrations=[FlaskIntegration()]
)
app = Flask(__name__)
socketio = SocketIO(app)
@app.route('/')
def index():
return '''
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
<script>
var socket = io()
</script>
Bedarf:
flask
sentry-sdk[flask]
flask-socketio
eventlet
Beispiel Gunicorn-Konfiguration:
bind = '[::]:4444'
worker_class = 'eventlet'
accesslog = '-'
Beim Laden /
wird eine Verbindung zum Websocket hergestellt. Beim Trennen des Websockets (z. B. wegnavigieren, aktualisieren) wird eine Ausnahme wie folgt erzeugt:
[2020-09-23 07:24:49 +0000] [16303] [ERROR] Error handling request /socket.io/?EIO=3&transport=websocket&sid=29f4c1adfac343d6bc6db56acf8fd0ee
Traceback (most recent call last):
File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/workers/base_async.py", line 55, in handle
self.handle_request(listener_name, req, client, addr)
File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/workers/base_async.py", line 115, in handle_request
resp.close()
File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 402, in close
self.send_headers()
File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 318, in send_headers
tosend = self.default_headers()
File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 299, in default_headers
elif self.should_close():
File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 219, in should_close
if self.status_code < 200 or self.status_code in (204, 304):
AttributeError: 'Response' object has no attribute 'status_code'
2001:470:1f07:7eb:9dd4:254c:35d7:236c - - [23/Sep/2020:07:24:49 +0000] "GET /socket.io/?EIO=3&transport=websocket&sid=29f4c1adfac343d6bc6db56acf8fd0ee HTTP/1.1" 500 0 "-" "-"
Hinweis: Ich habe Sentry selbst noch nie benutzt. Dies ist nur von der Sentry-Erste- Schritte -Seite. Das Beispiel dsn
funktioniert gut für unseren Test.
Das Kommentieren integrations=[FlaskIntegration()]
wird dann den Fehler beseitigen (natürlich effektiv Sentry deaktivieren).
Für das, was es wert ist, kann gevent-websocket anstelle von eventlet ohne Fehler verwendet werden. Allerdings scheint es dann alle Anfragen zu bearbeiten.
Ok, habe etwas rumgespielt. Sieht so aus, als ob Sentry/Newrelic die Antwort umschließt. Ohne Sentry erhalten wir wie erwartet <eventlet.wsgi._AlreadyHandled object at 0x7fd0f5b1c0d0>
und Gunicorns EventletWorker.is_already_handled() stoppt die Iteration.
Wenn Sie jedoch Sentry verwenden, wird dies stattdessen zu etwas wie <sentry_sdk.integrations.wsgi._ScopedResponse object at 0x7f30155a5100>
, wodurch die Überprüfung fehlschlägt
Stattdessen könnten wir einen Blick auf Respiter werfen, um zu sehen, ob er leer ist. Werde morgen weiter schauen.
Okay, hier ist die Problemumgehung, die ich mir ausgedacht habe:
eventlet_fix.py:
siehe Bearbeiten unten
Und in meiner gunicorn config.py: worker_class = 'eventlet_fix.EventletWorker
.
Das Problem ist, dass sentry/newrelic die Antworten umschließt, sodass wir es nicht einfach mit ALREADY_HANDLED
von eventlet vergleichen können. Da die Art einer bereits bearbeiteten Anfrage darin besteht, dass gunicorns start_response
nicht aufgerufen wird, können wir stattdessen prüfen, ob ein Antwortstatus vorhanden ist.
Also habe ich den wsgi-Aufruf entführt, um dann nach einem Antwortstatus zu suchen und Antwortwerte nach Bedarf zu hacken. Dadurch kann die Anfrage weiterhin von Gunicorn protokolliert werden. Wenn stattdessen das ursprüngliche Verhalten beibehalten werden soll, kann stattdessen StopIteration
ausgelöst werden.
Das Hacken des Status auf 101 ist für unseren Anwendungsfall hier angemessen (flask-socketio websocket), aber ansonsten funktioniert es auch, es auf None zu belassen, da headers_sent
und should_close
auf True gezwungen werden.
Auch hier wird davon ausgegangen, dass start_response
nicht aufgerufen wurde, wenn status
nicht gesetzt ist, und daher die Anfrage extern "bereits behandelt" worden sein muss.
edit: Nicht gut. Muss neu bewertet werden. Wenn die Ausführung der Anfrage einige Zeit in Anspruch nimmt, wird start_response
nicht aufgerufen, bevor resp.status
überprüft wird.
edit2: Hier ist eine korrigierte Version mit einem gehackten Response-Iterator:
from functools import wraps
from gunicorn.workers.geventlet import EventletWorker as _EventletWorker
class HackedResponse:
def __init__(self, respiter, resp):
self.respiter = iter(respiter)
self.resp = resp
if hasattr(respiter, "close"):
self.close = respiter.close
def __iter__(self):
return self
def __next__(self):
try:
return next(self.respiter)
except StopIteration:
if not self.resp.status:
self.resp.status = "101" # logger derives status code from status instead of using status_code
self.resp.status_code = 101 # not actually needed since headers_sent/force_close result in status_code not being checked anymore
self.resp.headers_sent = True
self.resp.force_close()
raise
def wsgi_decorator(wsgi):
@wraps(wsgi)
def wrapper(environ, start_response):
respiter = wsgi(environ, start_response)
resp = start_response.__self__
return HackedResponse(respiter, resp)
return wrapper
class EventletWorker(_EventletWorker):
def load_wsgi(self):
super().load_wsgi()
self.wsgi = wsgi_decorator(self.wsgi)
Offensichtlich ist dies nur ein Affenfleck. Der eigentliche Fix könnte möglicherweise in handle_request
in base_async.py einfließen. Der Schlüssel kann darin bestehen, (indirekt) zu prüfen, ob start_response
aufgerufen wurde, nachdem respiter
durchlaufen wurde, entweder durch Prüfen von resp.status
(nur start_response
aufgerufen) oder resp.headers_sent
(Bestätigung, dass wir tatsächlich auf die Anfrage geantwortet haben).
@benoitc
@ziddey hat einen Weg gefunden, das Problem zu lösen.
@ziddey kurze Fragen für Ihr Beispiel (da ich keine Wache verwende).
@benoitc derzeit nicht in der Lage zu testen, aber siehe Traceback oben https://github.com/benoitc/gunicorn/issues/1852#issuecomment -697189261 und https://github.com/benoitc/gunicorn/blob/4ae2a05c37b332773997f90ba7542713b9bf8274/ gunicorn/workers/base_async.py#L107 -L140
Normalerweise würde is_already_handled
True zurückgeben und es würde einfach hier enden.
Da die Antwort jedoch umschlossen ist, funktioniert diese Methode nicht. Stattdessen wird die Ausführung fortgesetzt und schlägt in Zeile 115 fehl: resp.close()
versucht, Header zu senden, aber start_response
wurde nie aufgerufen, daher gibt es keinen Statuscode. Selbst wenn es das täte, würde es letztendlich offensichtlich scheitern.
Dies führt zu einem AttributeError, der erneut ausgelöst und vermutlich von handle_error
behandelt wird. Da die Anfrage bereits extern behandelt wurde, kann hier außer Log-Spam nichts schaden.
Ich kann nicht zu viel über Sentry sagen – ich benutze es auch nicht.
Ein Detail jedoch: Der derzeitige bereits behandelte Mechanismus führt zu keiner Zugriffsprotokollierung. Ich nehme an, dass dies technisch sinnvoll ist, da es keine Möglichkeit gibt zu wissen, wie es extern gehandhabt wurde. In meiner gehackten Antwort erzwinge ich den Statuscode auf 101, mit headers_sent
als True, damit der Handler fortfahren kann und die Anfrage weiterhin protokolliert wird.
Das Überprüfen resp.status
ist ein definitiver Test, um festzustellen, ob start_response
wurde.
@benoitc greift dies noch einmal auf. Um definitiver zu dem Schluss zu kommen, dass die Anfrage bereits bearbeitet wurde, könnte environ['gunicorn.socket']
stattdessen eine Art Proxy für das zugrunde liegende Objekt sein. Auf diese Weise kann es aufgezeichnet werden, wenn direkt auf den Socket zugegriffen wird (z. B. get_socket()
für Eventlet umschließen), und für etwas wie is_already_handled
verwendet werden
Es würde jedoch immer noch das Hacken eines Antwortstatus erfordern, wenn eine Zugriffsprotokollierung gewünscht wird.
Hilfreichster Kommentar
Bump @benoitc