-
Notifications
You must be signed in to change notification settings - Fork 37
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
New signal for changed Mutable
, non-blocking functions to obtain guards
#77
base: master
Are you sure you want to change the base?
Conversation
What's the use case for those Signals? Let's suppose you do something like this... choice_to_read(mutable).map(|mutable| {
// ...
}) Multi-threading operations can happen at any time. So in between the time that the So So the only safe non-blocking option is the
That is already the case, because futures-signals is a hybrid push-pull system, that means if the consumer doesn't poll the Signal, the Signal will do nothing. Which means that methods like
What is the use case for that? That will just cause stale updates, where the downstream Signal thinks that it has the most up-to-date value, but it doesn't. Even worse, those stale updates will happen randomly at unpredictable times, which would make debugging a complete nightmare.
Note that if you need guaranteed non-locking behavior, there is It's quite rare to actually need that behavior, in almost every program it's completely fine to lock. Locking is completely normal and very common (and quite fast!). A locking update takes ~5.6 nanoseconds, compared to ~1.6 nanoseconds for an atomic operation: For reference, 5.6 nanoseconds is 0.000000056 seconds. If a lock is the bottleneck in your program, there's probably something very wrong. The only time you might care about locking is if you have a hard realtime program which must make strict guarantees about latency. This is not common, and in that case you're most likely not using Signals or Futures in the first place. And for Signals in particular, the locking is only for a very short duration. If you're locking for a long time... don't do that, that's not good practice in general. So it would help if you explained why you're trying to avoid locks. |
Thank you for taking the time to review and respond to this PR.
It is ok for the
That is correct. It could be combined with the
How would one use existing or custom signal adapters to get a notification that a
This is correct.
A solution involving
...if the lock is not being held. If write locks are held for nontrivial periods of time, and there are many readers, all of those reader threads are going to block until the write lock is released.
Locking and blocking are different issues. Using an async RwLock like Tokio's would at least allow other tasks to make progress while waiting for a |
That is true, however it also doesn't give you the value, it only lets you know that the Mutable updated at some point in the past. In order to obtain the value you would need to lock. So what use case do you have where you only need to know that a Mutable was updated, and you don't care about its value at all?
And then what will you do if the try_lock fails? Nothing? I'm having a hard time envisioning a situation where that behavior is correct.
Yes, but my point is that during the throttle the Signal won't be polled at all, and so there won't be any read lock while it is being throttled.
You can't, I was giving it as an example of how Signals are consumer-driven, and so there are many situations where it won't lock, because the consumer didn't poll it.
You just shouldn't be doing that. In general the entire Future system (including executors) assume that locks will not be held for a long period of time. That's how the entire Future ecosystem works, it goes far beyond just futures-signals. It's quite bad practice in general in Rust to be holding locks for a long period of time. There are better ways to architect your program.
Could you give an example? I've never seen any reasonable code that would cause locking issues, especially with Signals.
Usinc async RwLock would be a huge performance regression for something which isn't an issue in practice, and it doesn't even fully solve the problem.
To be clear, I have no issue with adding in the |
There are various scenarios in which information that a change has taken place is sufficient to trigger some kind of action.
Some actions might be available even with only the limited information that a change has occurred. Atomics might give additional information without locking.
A long period of time is relative; One would probably consider
Agreed, it should not be the default and should be a separate struct altogether, for the rare cases in which async guards are required. Async waiting for unlock (at least in the context of signals) can be done by using |
That's fine and all, but what's your use case? I generally don't add in features based on hypotheticals, because it's always possible to invent hypothetical situations, and so if I add in features just because it might be useful in some obscure situation, that leads to bloat and an infinite increase of features.
If lock contention is genuinely an issue, it would be easy to just change the implementation of the Signal polling, fixing it directly. That would be far better than adding in new APIs to hack around the problem.
Yes, but thankfully the Signal system is designed so that lock contention is minimized. This is the flow of operations:
Because the polling of the dependent Signals happens after the write lock is released, under typical situations there shouldn't be any lock contention at all. Of course you can create contrived situations where contention does happen, but it should be quite rare in practice, especially because the Signal system is designed to have very fine-grained mutability. futures-signals is designed to have lots and lots of Mutables, in general 1 Mutable per field. So a single struct might contain a dozen Mutables. And those Mutables can then contain sub-Mutables, which can contain sub-Mutables, etc. This spreads out the locks so that contention doesn't happen. So instead of linking to a thread which talks about hypothetical lock contention (unrelated to futures-signals), could you please explain your situation where you are seeing issues with locks? |
MutableSignalRef
,MutableSignal
, andMutableSignalCloned
all attempt to obtain a read lock on the underlyingRwLock
when polled. This can block the thread that is polling the signal, if theRwLock
is locked for writes.This may not be desirable behavior. This PR introduces a new signal
ChangedSignal
, that emits()
. It does not obtain a read lock when polled, and therefore will not block when polled.This PR also introduces non-blocking variants of
lock_ref
andlock_mut
(try_lock_ref
andtry_lock_mut
), similar to those exposed by the standard libraryRwLock
.ChangedSignal
can be used to implement more sophisticated signals, that give the user the option to read an underlying value if desired. For example:The signal in the
choice_to_read
example would be emitted whenever there is a change to the underlying value. The consumer of the signal is in control of when the value is read.In
fresh_or_ignore
, a signal is emitted whenever a value is updated IFF it is available to read when polled.