feat(runtime-sdk): Add serialize option in WSGI entrypoint - #240
feat(runtime-sdk): Add serialize option in WSGI entrypoint#240ryanking13 wants to merge 4 commits into
serialize option in WSGI entrypoint#240Conversation
|
LGTM! |
| body, | ||
| on_close=request_lock.release, | ||
| ) | ||
| request_lock.defer_release() |
There was a problem hiding this comment.
Why do we unconditionally call defer_release()? I guess the idea of the context manager is to release the lock on all the unsuccessful paths, but the success path will release it via the on_close method.
There was a problem hiding this comment.
Maybe could use a comment.
| if not self._enabled: | ||
| return | ||
|
|
||
| self._release_on_exit = False |
There was a problem hiding this comment.
Don't we need to set this back to True at some point?
There was a problem hiding this comment.
Okay I get it, RequestLock is only used once.
| return lock | ||
|
|
||
|
|
||
| class RequestLock: |
There was a problem hiding this comment.
Perhaps this should be called MaybeRequestLock? We could get rid of all the if self._enabled: guards by doing:
def __new__(cls, enabled: bool):
if enabled:
return object.__new__(cls)
return contextlib.nullcontext()| _close_iterable(result) | ||
| finally: | ||
| try: | ||
| environ["wsgi.input"].close() | ||
| except Exception: # noqa: BLE001 - best-effort cleanup | ||
| logger.exception("Failed to close wsgi.input") | ||
| finally: | ||
| if on_close is not None: | ||
| on_close() |
There was a problem hiding this comment.
Slightly confusing here with the nested try/finallys. So what we do is:
- always call
_close_iterable(),environ["wsgi.input"].close(), andon_close()in that order independently of which ones raise. - Errors in
environ["wsgi.input"].close()are always suppressed (except XCPU). - Errors in
_close_iterable()andon_close()propagate up the step - If both
_close_iterable()andon_close()raise, the error fromon_close()wins.
There was a problem hiding this comment.
It occurs to me that one general concern is if we have:
try:
do_something()
finally:
cleanup()it's possible that do_something() is stopped by an exceeded cpu error, and then cleanup() raises a different error replacing it, which gets caught and we lose track of the fact that we were supposed to stop early. This isn't really a problem introduced by this PR of course and you'd have to replace it with something awful like:
run_finally = True
try:
do_something()
except BaseException as e:
# prevent finally block from preempting BaseExceptions
run_finally = isinstance(e, Exception)
raise
finally:
if run_finally:
cleanup()
hoodmane
left a comment
There was a problem hiding this comment.
I think the logic all makes sense but is a bit messy and confusing. I also have some lingering doubts about whether we really need this, do we have any examples of wsgi apps that break without it?
Not yet. Maybe we can wait until we found a compelling example that needs this. For now people can manually use |
As we discussed internally, this adds a
serializeoption to wsgi handler so that users can serialize each request if there applications are not async safe.Originally, I thought we should add this to
asgiref.sync.sync_to_async, but I realized that WSGI applications don't useasgiref.sync.sync_to_asyncat all, and it is for using synchronous ORMs inside async handlers in ASGI applications. So I ended up locking the entire request.We can probably add the same option to asgi.py as well, but asgi.py is a bit more compliated due to lifespans, so I would like to discuss and land this to wsgi first.