#36439: Auth hashing blocks event loop if using asyncio
-------------------------------------+-------------------------------------
     Reporter:  robertaistleitner    |                     Type:
                                     |  Cleanup/optimization
       Status:  new                  |                Component:
                                     |  contrib.auth
      Version:  5.1                  |                 Severity:  Normal
     Keywords:  async, auth,         |             Triage Stage:
  asyncio, performance               |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 To a large extent, issues with auth in async auth has been fixed in
 
https://github.com/django/django/commit/50f89ae850f6b4e35819fe725a08c7e579bfd099,
 I guess this is the last part of proper async implementation of
 authentication.

 There exist custom implementations for asyncio regarding checking a
 password using the configured hashers which can be found in
 
https://github.com/django/django/blob/68c9f7e0b79168007e6ba0139fd315d7c44ca8c9/django/contrib/auth/hashers.py#L86-L91.

 This implementation has a flaw because the CPU heavy calculation of the
 hash is blocking the event loop of asyncio, causing the whole server to
 stall and queueing up all the following authentications that may rush in
 in case of heavy load.

 My proposal is to use a ThreadPoolExecutor here to be able to unload the
 work from the event loop:

 {{{
 CHECK_PASSWORD_THREAD_POOL_EXECUTOR = ThreadPoolExecutor(16)

 async def acheck_password(password, encoded, setter=None,
 preferred="default"):
     """See check_password()."""

     # verify_password is cpu heavy and needs to be executed in a separate
 thread to not block a running asyncio event loop
     is_correct, must_update = await sync_to_async(
         verify_password, thread_sensitive=False,
 executor=CHECK_PASSWORD_THREAD_POOL_EXECUTOR
     )(password, encoded, preferred=preferred)

     if setter and is_correct and must_update:
         await setter(password)
     return is_correct
 }}}

 The number of available thread could be exposed via setting, or skipped
 altogether by using a new thread on each verify_password call. What are
 your thoughts on this?
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36439>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-updates+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/010701973f3b447a-e72844a3-af90-4e4b-9d83-6c3cbf2050e7-000000%40eu-central-1.amazonses.com.

Reply via email to