Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AttributeError: asgiref.local._CVar object has no attribute 'value' #485

Open
natdempk opened this issue Dec 24, 2024 · 2 comments
Open

Comments

@natdempk
Copy link

natdempk commented Dec 24, 2024

Running the latest main (abc69a054aa440ef42c939b5a197df05c3ad48d2) in production to test a fix for #467, I see this error somewhat frequently across a wide spread of our requests/endpoints. This error was frequent enough that I reverted back to the old version which errors in a different way less frequently like #467.

Not really sure why __delattr__ in _CVar needs to throw if we try to delete an attribute that doesn't exist. Does this indicate a programming/synchronization bug somewhere or is this just a bit over-restricting? Not sure if this is bug prevention or its just fine.

Happy to test any further improvements/fixes here to try and get a release which doesn't have errors. Can also provide any additional info that might be helpful here if need be.

Relevant versions:

    "ddtrace==2.9.2",
    "django==4.2.13",

Stack trace (all failures are basically the same):

AttributeError: <asgiref.local._CVar object at 0x7f05030333a0> object has no attribute 'value'
  File "django/core/handlers/asgi.py", line 160, in __call__
    await self.handle(scope, receive, send)
  File "django/core/handlers/asgi.py", line 190, in handle
    await self.send_response(response, send)
  File "django/core/handlers/asgi.py", line 296, in send_response
    await sync_to_async(response.close, thread_sensitive=True)()
  File "concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "ddtrace/contrib/futures/threading.py", line 36, in _wrap_execution
    return fn(*args, **kwargs)
  File "django/http/response.py", line 335, in close
    signals.request_finished.send(sender=self._handler_class)
  File "django/dispatch/dispatcher.py", line 176, in send
    return [
  File "django/dispatch/dispatcher.py", line 177, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
  File "django/core/handlers/base.py", line 370, in reset_urlconf
    set_urlconf(None)
  File "django/urls/base.py", line 137, in set_urlconf
    del _urlconfs.value
  File "asgiref/local.py", line 129, in __delattr__
    delattr(storage, key)
  File "asgiref/local.py", line 38, in __delattr__
    raise AttributeError(f"{self!r} object has no attribute {key!r}")
@natdempk
Copy link
Author

natdempk commented Feb 6, 2025

@carltongibson -- do you have any opinions about a potential fix + validation of that fix here?

Basically related to:

Not really sure why __delattr__ in _CVar needs to throw if we try to delete an attribute that doesn't exist. Does this indicate a programming/synchronization bug somewhere or is this just a bit over-restricting? Not sure if this is bug prevention or its just fine.

From reading the docstring on Local

Unlike plain `contextvars` objects, this utility is threadsafe.

If this is true, to me this implies multiple threads? (actually confused here about what scenario threadsafety is referring to here, multiple threads sharing the context of an async task?) can call del. As defined __delattr__ is:

    def __delattr__(self, key):
        with self._lock_storage() as storage:
            delattr(storage, key)

And storage's __delattr__ is:

    def __delattr__(self, key: str) -> None:
        if key in self._data:
            del self._data[key]
        else:
            raise AttributeError(f"{self!r} object has no attribute {key!r}")

So if two threads? try to delete the same attribute, one is going to win and one is going to error, even though the operation is properly locked. It seems like it should be pretty harmless to just let a second delete go through here without error?

Though seemingly the standard behavior of del in python is to throw. Looking again at my stack trace a bit closer, this could be something upstream in Django interacting poorly here, but that might be a larger challenge to land/validate vs. just allowing repeated del here, especially if the issue stems from async task + thread coordination. What do you think?

@carltongibson
Copy link
Member

@natdempk I haven't had space to look into it yet, I'm afraid. This issue is on my list, along with the cluster around #493.

Alas WORK has left me little time of late. 🤹

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants