BUG, EASY, PY38: web_protocol.RequestHandler mismatch _keepalive field with __slots__ #3644
Description
Long story short
In RequestHandler
, the spelling in __slots__
is _keep_alive
but in the default assignment in __init__()
, the name of the field is _keepalive
. The latter is what actually gets used in the module, not the slots name.
In Python 3.7, this appears to be ok because instances are getting a __dict__
from somewhere (presumably from an ancestor in the MRO chain). However, in Python 3.8 it seems that instances of RequestHandler
are not getting a __dict__
attached to them, which is why an AttributeError
gets raised.
The slots declaration in aiohttp/web_protocol.py
:
class RequestHandler(BaseProtocol):
KEEPALIVE_RESCHEDULE_DELAY = 1
__slots__ = ('_request_count', '_keep_alive', '_manager',
'_request_handler', '_request_factory', '_tcp_keepalive',
'_keepalive_time', '_keepalive_handle', '_keepalive_timeout',
'_lingering_time', '_messages', '_message_tail',
'_waiter', '_error_handler', '_task_handler',
'_upgrade', '_payload_parser', '_request_parser',
'_reading_paused', 'logger', 'debug', 'access_log',
'access_logger', '_close', '_force_close')
def __init__(self, manager: 'Server', *,
loop: asyncio.AbstractEventLoop,
keepalive_timeout: float=75., # NGINX default is 75 secs
tcp_keepalive: bool=True,
logger: Logger=server_logger,
access_log_class: Type[AbstractAccessLogger]=AccessLogger,
access_log: Logger=access_logger,
access_log_format: str=AccessLogger.LOG_FORMAT,
debug: bool=False,
max_line_size: int=8190,
max_headers: int=32768,
max_field_size: int=8190,
lingering_time: float=10.0):
super().__init__(loop)
self._request_count = 0
self._keepalive = False # <----- NAME DIFFERS TO _keep_alive IN SLOTS
self._manager = manager # type: Optional[Server]
self._request_handler = manager.request_handler # type: Optional[_RequestHandler] # noqa
self._request_factory = manager.request_factory # type: Optional[_RequestFactory] # noqa
This code was added in #3095.
Expected behaviour
In Python 3.8 requests to the aiohttp web server should succeed.
Actual behaviour
In Python 3.8, requests fail with this traceback:
Exception in callback BaseProactorEventLoop._start_serving.<locals>.loop(<_OverlappedF...0.1', 64284))>) at G:\Programs\Python38\lib\asyncio\proactor_events.py:651
handle: <Handle BaseProactorEventLoop._start_serving.<locals>.loop(<_OverlappedF...0.1', 64284))>) at G:\Programs\Python38\lib\asyncio\proactor_events.py:651>
Traceback (most recent call last):
File "G:\Programs\Python38\lib\asyncio\events.py", line 81, in _run
self._context.run(self._callback, *self._args)
File "G:\Programs\Python38\lib\asyncio\proactor_events.py", line 658, in loop
protocol = protocol_factory()
File "G:\Documents\repos\blah\venv\lib\site-packages\aiohttp\web_server.py", line 57, in __call__
return RequestHandler(self, loop=self._loop, **self._kwargs)
File "G:\Documents\repos\blah\venv\lib\site-packages\aiohttp\web_protocol.py", line 136, in __init__
self._keepalive = False
AttributeError: 'RequestHandler' object has no attribute '_keepalive'
Immediately at the offending line (in a debugger), the __mro__
for the RequestHandler
instance appears to be the same when running under Python 3.7 and Python 3.8:
[
<class 'tuple'>: (<class 'aiohttp.web_protocol.RequestHandler'>,
<class 'aiohttp.base_protocol.BaseProtocol'>,
<class 'asyncio.protocols.Protocol'>,
<class 'asyncio.protocols.BaseProtocol'>,
<class 'object'>)
]
This leads me to suspect that something might have changed with either Protocol
or BaseProtocol
in upstream asyncio (i.e. declaring slots so as to prevent __dict__
appearing on the instance), but I've not confirmed this.
Regardless, the name mismatch of the field must still be fixed.
Steps to reproduce
Server code with:
import sys
from aiohttp import web
routes = web.RouteTableDef()
FILENAME = 'reveal/reveal.js-3.6.0/index.html' # Or whatever
@routes.get('/')
async def handle(request):
return web.FileResponse(FILENAME)
app = web.Application()
app.router.add_static('/js', 'reveal/reveal.js-3.6.0/js')
app.router.add_static('/css', 'reveal/reveal.js-3.6.0/css')
app.router.add_static('/img', 'reveal/reveal.js-3.6.0/img')
app.router.add_static('/lib', 'reveal/reveal.js-3.6.0/lib')
app.router.add_static('/plugin', 'reveal/reveal.js-3.6.0/plugin')
app.router.add_routes(routes)
web.run_app(app)
Then open localhost:8080
in a browser.
Your environment
- Windows 10 x64
- Python 3.8.0a2 (downloaded installer from python.org), with
requirements.txt
:
aiohttp==3.5.4
async-timeout==3.0.1
attrs==19.1.0
chardet==3.0.4
Cython==0.29.6
dominate==2.3.5
idna==2.8
multidict==4.5.2
numpy==1.16.2
yarl==1.3.0