Skip to content

Commit

Permalink
Merge pull request #190 from scipp/fix-deadlock
Browse files Browse the repository at this point in the history
Avoid deadlock caused by functools.cached_property
  • Loading branch information
SimonHeybrock committed Jan 24, 2024
2 parents 1c5f0fa + 93a3ae6 commit a370773
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 3 deletions.
61 changes: 61 additions & 0 deletions src/scippnexus/_cache.py
@@ -0,0 +1,61 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2024 Scipp contributors (https://github.com/scipp)
"""
We avoid functools.cached_property due to a per-instance lock which causes
deadlocks in multi-threaded use of ScippNexus. This lock is removed in
Python 3.12, so we can remove this file once we drop support for Python 3.11.
This file contains a 1:1 backport of Python 3.12's cached_property.
"""
# flake8: noqa: E501
from types import GenericAlias

_NOT_FOUND = object()


class cached_property:
def __init__(self, func):
self.func = func
self.attrname = None
self.__doc__ = func.__doc__

def __set_name__(self, owner, name):
if self.attrname is None:
self.attrname = name
elif name != self.attrname:
raise TypeError(
"Cannot assign the same cached_property to two different names "
f"({self.attrname!r} and {name!r})."
)

def __get__(self, instance, owner=None):
if instance is None:
return self
if self.attrname is None:
raise TypeError(
"Cannot use cached_property instance without calling __set_name__ on it."
)
try:
cache = instance.__dict__
except (
AttributeError
): # not all objects have __dict__ (e.g. class defines slots)
msg = (
f"No '__dict__' attribute on {type(instance).__name__!r} "
f"instance to cache {self.attrname!r} property."
)
raise TypeError(msg) from None
val = cache.get(self.attrname, _NOT_FOUND)
if val is _NOT_FOUND:
val = self.func(instance)
try:
cache[self.attrname] = val
except TypeError:
msg = (
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
f"does not support item assignment for caching {self.attrname!r} property."
)
raise TypeError(msg) from None
return val

__class_getitem__ = classmethod(GenericAlias)
3 changes: 2 additions & 1 deletion src/scippnexus/base.py
Expand Up @@ -6,13 +6,14 @@
import inspect
import warnings
from collections.abc import Mapping
from functools import cached_property, lru_cache
from functools import lru_cache
from types import MappingProxyType
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union, overload

import numpy as np
import scipp as sc

from ._cache import cached_property
from ._common import to_child_select
from .attrs import Attrs
from .field import Field
Expand Down
2 changes: 1 addition & 1 deletion src/scippnexus/field.py
Expand Up @@ -6,7 +6,6 @@
import re
import warnings
from dataclasses import dataclass
from functools import cached_property
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union

Expand All @@ -18,6 +17,7 @@
from scippnexus._hdf5_nexus import _warn_latin1_decode
from scippnexus.typing import H5Dataset, ScippIndex

from ._cache import cached_property
from .attrs import Attrs

if TYPE_CHECKING:
Expand Down
2 changes: 1 addition & 1 deletion src/scippnexus/nxdata.py
Expand Up @@ -4,13 +4,13 @@
from __future__ import annotations

import uuid
from functools import cached_property
from itertools import chain
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union

import numpy as np
import scipp as sc

from ._cache import cached_property
from ._common import _to_canonical_select, convert_time_to_datetime64, to_child_select
from .base import (
Group,
Expand Down

0 comments on commit a370773

Please sign in to comment.