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

Make classproperty and lazyproperty thread-safe #11224

Merged
merged 8 commits into from
Jan 12, 2021

Conversation

bmerry
Copy link
Contributor

@bmerry bmerry commented Jan 8, 2021

Description

This pull request is to address thread safety in the decorator utilities.

Fixes #11221.

I've also added unit tests. Testing race conditions is notoriously tricky, so I've made it try 10000 times each. That adds about 1s (for each), which is my attempt at a compromise between not slowing down the tests and trying enough times to trigger the bug. Without the fix it is still not enough to reliably fail, but it should be enough that if someone breaks it in future it will fail sooner or later in CI and prompt someone to look closer.

Fixes astropy#11221. A double-checked locking pattern is used to avoid
impacting performance in the case where the property is already
initialised. For lazyproperty, setter and deleter do take the lock,
but I'm assuming those are not as performance-critical as the getter.
@github-actions github-actions bot added the utils label Jan 8, 2021
Now that I've created the PR I have the PR number.
It was listed under API changes, should be bug fixes.
bmerry added a commit to bmerry/astropy that referenced this pull request Jan 8, 2021
The lexer and parser generation is all collected together in one place
(a new astropy.utils.parsing module), with the side benefit that a lot
of code duplication is eliminated. This new shared code uses appropriate
locking to ensure that both parser creation and use is thread-safe.

Fixes astropy#11225 (when combined with astropy#11224).
Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bmerry - very nice as well! I'm approving, though it may be good for @embray to have a look as well.

@mhvk mhvk added this to the v4.3 milestone Jan 8, 2021
@mhvk
Copy link
Contributor

mhvk commented Jan 8, 2021

@embray, @bsipocz - for these thread-safety PRs there is a general question whether we should treat them as bugs to be backported. For now, I've left the milestone at 4.3 (consistent with where @bmerry put the changelog entry).

@bmerry
Copy link
Contributor Author

bmerry commented Jan 11, 2021

Thanks for the fast reviews. I have one more question: where should the fast_thread_switching fixture go so that it can be shared between tests in different modules? Is the top-level astropy/conftest.py appropriate?

@bmerry
Copy link
Contributor Author

bmerry commented Jan 11, 2021

I've been thinking about this a bit more: it's probably not necessary to take the lock in the setter and deleter, because if one thread is setting/deleting at the same time as another is accessing the property, the user's code has a race condition anyway. We just need to allow concurrent reads to be safe.

If the user is calling these concurrently from multiple threads, they
already have a race condition in their code (as opposed to using the
getter from multiple threads, which they can have a reasonable
expectation of working).

I did change the deleter to do the deletion atomically, so that as long
as no fdel is provided it'll be safe to do concurrent deletion. This
should restore performance for classes that use a lazyproperty as a
cache and need to invalidate the cache when attributes change (e.g.
Time).
@bmerry
Copy link
Contributor Author

bmerry commented Jan 11, 2021

I've been thinking about this a bit more: it's probably not necessary to take the lock in the setter and deleter

I've pushed a change for that.

I'd added alternate code for deleting, but failed to remove the
original.
@mhvk
Copy link
Contributor

mhvk commented Jan 11, 2021

Yes, not guarding the setter/deleter makes sense. As for the location, I'm not sure, maybe @embray has a suggestion... I'll admit that I've simply imported from another test function elsewhere, but it is obvious that eventually that is just going to be a hopeless mess.

Copy link
Member

@pllim pllim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

classproperty is used in io.fits and coordinates too, so maybe @saimn and @adrn want to have a look as well.

As for lazyproperty, it just have to not break existing code until we can drop Python 3.7 and then switch to using cached_property (#9036).

I also wonder if we need a temporary commit to enable running "exotic" architectures and also pyinstaller in this PR (and other PRs on the topic of multi-threading). They are defined in other YAML files inside .github/workflows.


This makes it easier to provoke race conditions.
"""
old = sys.getswitchinterval()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as #11226 (comment) . That is, define this in setup_module and teardown_module in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's appropriate to use for the entire module. It will likely slow down the rest of the tests in the module.

I'm also expecting that we'll find more thread safety issues in other modules and write more regression tests, so avoiding duplication of this code seems worthwhile (particularly since I don't yet know what the best value for the magic number is).

@bmerry
Copy link
Contributor Author

bmerry commented Jan 12, 2021

I've moved the fast_thread_switching fixture to astropy/conftest.py. There is already another fixture in there (ignore_matplotlibrc) so it seemed like a reasonable place for it.

@bmerry
Copy link
Contributor Author

bmerry commented Jan 12, 2021

I also wonder if we need a temporary commit to enable running "exotic" architectures

What is the motivation for that? Note that the thread safety issues I've been fixing are all in Python code, which means that the threads aren't really running in parallel (they're serialised by the GIL), and hence ISA memory models are unlikely to have any impact.

@saimn
Copy link
Contributor

saimn commented Jan 12, 2021

Sounds good to me 👍

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing I've never liked much about pytest fixtures in conftest is that it becomes quite hard to find out where something is defined. But I don't really have a better suggestion and at least the na me of the fixture does tell what it does... Does anyone else have a better suggestion? If not, I suggest we merge this - it is nice!

Copy link
Member

@pllim pllim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, conftest.py seems like a good place as any. 👍

As for the exotic arch, it's more of a sanity check but if we don't do it here, we'll find out soon enough when the cron runs, and I know who to ping. 😉

Let's get this in then, so people have plenty of time to test against this in 4.3.dev before feature freeze time.

Thank you, all!

@pllim pllim merged commit a1dbb20 into astropy:master Jan 12, 2021
@embray
Copy link
Member

embray commented Jan 16, 2021

@bmerry Thanks for this. lazyproperty was of course written many years ago, long before Python added cached_property. As not a lot of people were using astropy in multi-threaded code it was never intended to be thread-safe, but I'm glad you caught this and fixed it.

namurphy pushed a commit to namurphy/astropy that referenced this pull request Feb 21, 2021
The lexer and parser generation is all collected together in one place
(a new astropy.utils.parsing module), with the side benefit that a lot
of code duplication is eliminated. This new shared code uses appropriate
locking to ensure that both parser creation and use is thread-safe.

Fixes astropy#11225 (when combined with astropy#11224).
namurphy pushed a commit to namurphy/astropy that referenced this pull request Feb 21, 2021
The lexer and parser generation is all collected together in one place
(a new astropy.utils.parsing module), with the side benefit that a lot
of code duplication is eliminated. This new shared code uses appropriate
locking to ensure that both parser creation and use is thread-safe.

Fixes astropy#11225 (when combined with astropy#11224).
namurphy pushed a commit to namurphy/astropy that referenced this pull request Feb 21, 2021
The lexer and parser generation is all collected together in one place
(a new astropy.utils.parsing module), with the side benefit that a lot
of code duplication is eliminated. This new shared code uses appropriate
locking to ensure that both parser creation and use is thread-safe.

Fixes astropy#11225 (when combined with astropy#11224).
namurphy pushed a commit to namurphy/astropy that referenced this pull request Feb 21, 2021
The lexer and parser generation is all collected together in one place
(a new astropy.utils.parsing module), with the side benefit that a lot
of code duplication is eliminated. This new shared code uses appropriate
locking to ensure that both parser creation and use is thread-safe.

Fixes astropy#11225 (when combined with astropy#11224).
nstarman pushed a commit to nstarman/astropy that referenced this pull request Feb 28, 2021
The lexer and parser generation is all collected together in one place
(a new astropy.utils.parsing module), with the side benefit that a lot
of code duplication is eliminated. This new shared code uses appropriate
locking to ensure that both parser creation and use is thread-safe.

Fixes astropy#11225 (when combined with astropy#11224).
Akshat1Nar pushed a commit to Akshat1Nar/astropy that referenced this pull request Mar 9, 2021
The lexer and parser generation is all collected together in one place
(a new astropy.utils.parsing module), with the side benefit that a lot
of code duplication is eliminated. This new shared code uses appropriate
locking to ensure that both parser creation and use is thread-safe.

Fixes astropy#11225 (when combined with astropy#11224).
@pllim pllim mentioned this pull request Nov 23, 2021
9 tasks
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request Aug 31, 2022
2.0.0.1 (2021-11-02)
====================

- The underlying universal functions in ``erfa.ufunc`` now work with an ``out``
  argument also if the required output is a structured array. [gh-76]

2.0.0 (2021-05-17)
==================

- Bundled liberfa version update to v2.0.0. This includes new functionality,
  and hence pyerfa 2.0.0 cannot run with previous versions of liberfa.
- ``erfa.dt_eraLDBODY`` has been corrected to ensure that the 'pv' entry is
  now of type ``erfa.dt_pv``, so that cross-assignments with that dtype work
  correctly. [gh-74]
- ``erfa_generator`` now also generates a ``test_ufunc.py`` file that
  runs all the C code tests on the ufuncs, thus verifying the code
  wrapping worked correctly. As part of that, the ability to give
  specific output file names has been removed, as it was not used.
  (Note: these changes have no effect on use of PyERFA.) [gh-71]

1.7.3 (2021-04-25)
==================

- Bundled liberfa version update to v1.7.3.
- Fixed a bug that caused the output of ``rx``, ``ry``, and ``rz`` to be
  boolean rather than float for some compilers/OS. [gh-72]

1.7.2 (2021-01-25)
==================

- Bundled liberfa version update to v1.7.2.
- The classproperty decorator is now thread-safe
  (backport astropy/astropy#11224).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lazy properties are not threadsafe
5 participants