#26332: `BaseCache.get_or_set()` violates principle of least astonishment
-------------------------------------+-------------------------------------
     Reporter:  suligap              |                    Owner:  nobody
         Type:  Uncategorized        |                   Status:  new
    Component:  Core (Cache system)  |                  Version:  1.9
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  1                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Description changed by suligap:

Old description:

> [https://github.com/django/django/blob/2109975e901440da70e29d0f330a600bc2d37e9a/django/core/cache/backends/base.py#L150-L169
> BaseCache.get_or_set()] returns `False` if the key was set in cache
> between the first `.get()` and `.add()`.
>
> The reason for using `.add()` instead of `.set()` in there is a "big race
> condition" as stated in [https://code.djangoproject.com/ticket/12982
> 12982]. However, it is very unlikely that the caller will pass a
> `default` callable that reads the key from the cache and returns a result
> that depends on the value currently stored in cache which would be a
> prerequisite for a race condition to occur. Using `set()` as originally
> stated in the description of [https://code.djangoproject.com/ticket/12982
> 12982] means that there will be a ''data race'' which doesn't affect the
> correctness of the program, because the result is that multiple writes
> happened, but all of them are correct.
>
> Following texts contain a nice explanation of what is a race condition
> and how it relates to a data race:
> - [http://blog.regehr.org/archives/490 Race Condition vs. Data Race]
> - [https://support.microsoft.com/en-us/kb/317723 Description of race
> conditions and deadlocks]
>
> As a user of `get_or_set()` I am expecting it to always return a value,
> regardless if a write to the cache happened and it is inefficient for me
> to handle the `False` return scenario by calling my `default()` callback
> again to obtain the value I need (it was already executed inside
> .get_or_set, but the result was discarded).
>
> I can see 2 possible fixes:
> - always use `.set()` and always return the value of default
> - ignore the result of `.add()` and always return to the user with the
> result of `.get(key, default)` after the call to `.add()`

New description:

 
[https://github.com/django/django/blob/2109975e901440da70e29d0f330a600bc2d37e9a/django/core/cache/backends/base.py#L150-L169
 BaseCache.get_or_set()] returns `False` if the key was set in cache
 between the first `.get()` and `.add()`.

 The reason for using `.add()` instead of `.set()` in there is a "big race
 condition" as stated in #12982. However, it is very unlikely that the
 caller will pass a `default` callable that reads the key from the cache
 and returns a result that depends on the value currently stored in cache
 which would be a prerequisite for a race condition to occur. Using `set()`
 as originally stated in the description of #12982 means that there will be
 a ''data race'' which doesn't affect the correctness of the program,
 because the result is that multiple writes happened, but all of them are
 correct.

 Following texts contain a nice explanation of what is a race condition and
 how it relates to a data race:
 - [http://blog.regehr.org/archives/490 Race Condition vs. Data Race]
 - [https://support.microsoft.com/en-us/kb/317723 Description of race
 conditions and deadlocks]

 As a user of `get_or_set()` I am expecting it to always return a value,
 regardless if a write to the cache happened and it is inefficient for me
 to handle the `False` return scenario by calling my `default()` callback
 again to obtain the value I need (it was already executed inside
 .get_or_set, but the result was discarded).

 I can see 2 possible fixes:
 - always use `.set()` and always return the value of default
 - ignore the result of `.add()` and always return to the user with the
 result of `.get(key, default)` after the call to `.add()`

--

--
Ticket URL: <https://code.djangoproject.com/ticket/26332#comment:2>
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 [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/065.a64c0a406a34a82243a1fa40abb5fb0f%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to