Clean up Feature Flags API, add support for scoped feature tests - #9881
Conversation
74367c8 to
ae861f0
Compare
| unixstring = "0.2.7" | ||
| widestring = "1.0.2" | ||
| rand_pcg = "0.3.1" | ||
| parking_lot = "0.12.1" |
There was a problem hiding this comment.
parking_lot is a drop-in replacement for the standard sync primitives?
We should not have multiple ways of doing the same thing without a good reason.
There was a problem hiding this comment.
The std::sync::RwLock does not support upgradeable readers/downgradeable writer, which is necessary for scoped_test to atomically change from a writer to a reader for the test to actually read the feature value. Sadly the diff for adding scoped_test got mangled1. This is getting added to avoid race-conditions as tests in Rust are multi-threaded by default, unlike the C++ ones, see #9854 (comment) for where this will be used.
We should be able to use parking_lot as a dev-dependency and use std::sync:RwLock otherwise, but I don't see the point in doing so as it just causes unnecessary cfg()'s ,and parking_lot::RwLock is generally faster afaik https://crates.io/crates/parking_lot
Footnotes
-
Overall diff still mangled, but the individual commit is much nicer https://github.com/fish-shell/fish-shell/pull/9881/commits/d9857f28cbc98e2fbbc047de3402941db0465506 ↩
There was a problem hiding this comment.
can we tell Rust to not run this test in parallel to others?
In Go you have to explicitly enable parallelism for tests..
There was a problem hiding this comment.
That requires either cargo test -- --test-threads=1 or using a separate test-runner like https://nexte.st and using test groups https://nexte.st/book/test-groups.html
|
|
||
| /// The singleton shared feature set. | ||
| static mut global_features: Features = Features::new(); | ||
| static FEATURES: RwLock<Features> = RwLock::new(Features::new()); |
There was a problem hiding this comment.
I don't see a reason for using locking instead of atomic bools.
(Also our current stance is to use the simpler Mutex unless we really need concurrent multiple readers)
IIRC feature flags are set early on during startup and never changed.
From that PoV, atomic bools make most sense because they are usually zero-overhead
There was a problem hiding this comment.
TBH this change can be #[cfg(test)]'d. I don't see the point arguing about the performance for functions that are called very rarely. This is a RwLock since we need to guarantee that a test that's dependant on one particular feature flag actually has that value during the test. AtomicBool does not work for that, and neither does a Mutex, unless I am misunderstanding how you are suggesting to use the mutex.
ae861f0 to
8e379ee
Compare
This also cleans up and removes unnecessary usage of FFI-oriented `feature_metadata_t`, which is only used from Rust code after `builtins/status` was ported.
8e379ee to
d9857f2
Compare
|
The cleanup commit looks good, thank you! I agree with krobelus here: I am generally suspicious of read-write locks, and I would prefer for the common case to be a simple read of a global boolean, and not have the design be overly influenced by the needs of the tests. How about introducing a thread-local The advantage is that |
I don't think it matters enough to argue about. Changed it to a thread-local |
f5bbec5 to
5a3f3bc
Compare
5a3f3bc to
2a8a474
Compare
This also allows scoped feature tests that makes testing feature flags thread-safe. As in you can guarantee that the test actually has the correct feature flag value, regardless of which other tests are running in parallell.
2a8a474 to
feeed4e
Compare
| #[cfg(not(any(test, feature = "fish-ffi-tests")))] | ||
| { | ||
| FEATURES.set_from_string(wstr) | ||
| } |
There was a problem hiding this comment.
BTW I don't think this cfg is built in CI, as if so it should've failed in one of my pushes
mqudsi
left a comment
There was a problem hiding this comment.
Instead of using parking_lot + RwLock, we can use an atomic counter and a mutex, no?
I feel like features are such a fringe feature that they shouldn't impose such a burden to the rest of the code. The use of unsafe for features support isn't really the end of the world.
I really do not see any point in rolling our own atomic/sync container implementation just for features, this is not important for performance and I want correctness without having to think about it.
By using
As noted in the PR description unsafe was not really needed, the API was just garbage, as you needed a |
|
Merged, thank you! |
yeah this was ported literally from C++ |
And that's a sort of mismatch that is to be expected in a port like this. There is going to be a certain amount of friction between C++ and Rust, and a fairly literal port is the best way to make it tractable - divide and conquer, so you don't get lost in architectural adventures. The trick is in knowing when not to do that. And as a side note: While I know that calling someone else's code "garbage" is common in other projects, I would prefer not to have that here. |
Poor choice of words from my side. "Inconsistent" would have been more appropriate |
Previously you needed
unsafeto set feature flags from Rust. This also allowsscoped feature tests that makes testing feature flags thread-safe.
Note for posterity: you only needed
unsafesince theset/set_from_stringtook&mut selfwhen they could take&selfsince the feature flags are stored in atomics