geertj / gruvi

Async IO for Python, Simplified
http://gruvi.readthedocs.org/
MIT License
94 stars 12 forks source link

Feature request: Notification of connection close/other errors to client. #11

Closed jmooreoliva closed 10 years ago

jmooreoliva commented 10 years ago

Note: I am aware that I can use a timeout to prevent infinite hanging. I am also aware that it is not guaranteed to get a close notification over the network. However, for calls that may normally take a long time to complete, being notified of a closed connection (if possible) when it occurs is useful to allow for faster retries/faster cleanup of resources.

To demonstrate, I have two webservers below. gevent_server.py and gruvi_server.py. It does not matter which server you run for this issue (they are both included just to demonstrate that the issue is on the client, not the server side).

I have also included two clients: urllib3_client.py and gruvi_client.py. urllib3_client.py detects the closed connection and raises an exception, while gruvi_client.py does not give any indication that anything has happenned.

It would be nice if the client could give some indication (via an exception preferably) that the connection has been closed.

I am assuming that there are other client errors other than connection close that are probably being ignored here as well. My guess is that error results are being ignored from whatever pyuv call is being made to read data, similar to issue#9.

Client output:

$ python urllib3_client.py 
Traceback (most recent call last):
<snip traceback>
    raise ConnectionError('Connection failed.', e)
urllib3.exceptions.ConnectionError: ('Connection failed.', BadStatusLine("''",))

$ python gruvi_client_clean.py 
connecting
None
requesting
None
reading

(Ctrl+C to stop here, no notification of close).

gruvi_client.py

from gruvi.http import *

import logging

import sys

logger = logging.getLogger()
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
template = '%(levelname)s %(message)s'
handler.setFormatter(logging.Formatter(template))
logger.addHandler(handler)

try:
    client = HttpClient()

    print 'connecting'
    print client.connect(('localhost', 8444))

    print 'requesting'
    print client.request('POST', '/api/v0/get_config')

    print 'reading'
    response = client.getresponse()

    print 'complete reading'

    client.close()

    print response.status
    print response.read()
except Exception:
    logging.error('Error.', exc_info=True)

urllib3_client.py

from datetime import datetime
import urllib3

http = urllib3.PoolManager()

r = http.urlopen('POST', 'http://localhost:8444/api/v0/get_config', retries=False)

print r.status
print r.data

gruvi_server.py

from datetime import datetime
import logging
import os
import sys

import gruvi
from gruvi import get_hub
from gruvi.http import HttpServer

logging.basicConfig()

def hello_app(environ, start_response):
    print environ['REQUEST_URI'], datetime.now()
    gruvi.sleep(15)
    print 'exiting'

    sys.exit(0)

server = HttpServer(hello_app)

server.listen(('0.0.0.0', 8444))

hub = get_hub()
print 'Press CTRL-C to exit'
hub.switch()

gevent_server.py

from gevent import monkey; monkey.patch_all()

from datetime import datetime
import os
import time
import sys

import bottle

@bottle.post('/api/v0/get_config')
def get_config():
    print 'get_config', datetime.now()

    time.sleep(15)
    print 'exiting'
    #sys.exit(0) returns 500
    os._exit(0)

bottle.run(quiet=True, server='gevent', port=8444, host='0.0.0.0')
geertj commented 10 years ago

Thanks! I fixed it.

Note to self: it is not right to use a protocol's queue like this to pass exceptions. There will be some refactoring here in the weekend.