-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
ConflatedChannel unexpected behavior #332
Comments
It works as expected. Close token is treated equally with any other elements and definition of conflated channel states that "the receiver always gets the most recently sent element" which is close token is case of Could you please elaborate on your use-case, why do you need a conflated channel with special "close" treatment? An example from a real application would be appreciated, maybe we can work on better alternative or provide better documentation |
fun getResponseAsync(request: ApiRequest): ReceiveChannel<ResponseType> {
val responseChannel = ConflatedChannel<ResponseType>()
launch {
val cachedResponse = getCachedResponse(request);
if (cachedResponse != null) {
responseChannel.send(cachedResponse)
}
if (cachedResponse == null || isExpired(cachedResponse)) {
try {
val newResponse = callApiAndCacheResponse(request)
responseChannel.send(newResponse)
catch(apiError: Throwable) {
responseChannel.close(apiError)
}
}
responseChannel.close()
}
return responseChannel
} I use this code to make an application always show latest data in UI, the UI flow is:
When |
Hi @qwwdfsad async {
getCachedResponse(request)
?.takeUnless{ isExpired(it) }
?: callApiAndCacheResponse(request)
} or simply suspend fun getResponse(request: ApiRequest) =
getCachedResponse(request)
?.takeUnless{ isExpired(it) }
?: callApiAndCacheResponse(request)
val response = getResponse(request) |
I've removed my answer because it's not what @Khang-NT actually wants |
Hi all,
|
I aggree with @Khang-NT, there is either a problem or a missing channel implementation. Here is a shorter example : fun main(args: Array<String>) = runBlocking {
val source = produce(Unconfined, Channel.CONFLATED) { send(1) ; send(2) }
println(source.receive())
} This code will always throw I would expect to receive the latest value (2) and not an exception. As far as I know there is no other channel allowing me to do so. More generally the problem is that it will be rarely possible to get the latest value of a conflated channel created with |
suspend fun receive(): E
|
@fvasco yes it behaves correctly according to the documentation. Sorry, I wasn't clear. I'm not saying it is a bug. I am saying it is a design problem. Why the close token should override the latest value of a conflated channel? Note: it is actually possible to get the latest value, but the receive has to be called after the last Actually even close documentation is misleading:
And in this cases previously sent elements are not received. However design problem or not, how can we solve the folowing?: @Test
fun userShouldBeAbletoReceiveTheLastSentElement() = runBlocking {
val source = produce(Unconfined, <use whatever channel suits the need>) { send(1) ; send(2) }
assertThat(source.receive(), equalsTo(2))
} |
@qwwdfsad, any update on this? I find a bit cumbersome not being able to use conflated channel when I only care about the most recent value sent by a producer. |
@jcornaz nope. Could you please clarify which Rx primitive are you using for behaviour like conflated channel + don't throttle the last element? In general, it's not clear how to implement the desired behaviour and still maintain channel contract.
Maybe it's not a good idea to call |
To me And even that is a bit exaggerated. In RxJava once subscribed, the subscriber will receive all elements. This is not the case with the open
I don't. I haven't said that I have a better way of doing it with RxJava. But I am not aware of an equivalent concept of
It should do what is currently stated in the documentation:
which is not the case for
Same as it does currently and same as it behave for any other channel. Sorry, I don't understand why it is a problem. Could you please elaborate?
Good point. However what if I know that from a given point of time the value will never change anymore? It is a bit sad that we cannot close the channel to tell the receivers they can finish and release their resources. (we cannot because it may cause to loose the last item which can lead to incorrect results) |
You are right. From my perspective ConflatedBroadcastChannel with one subscriber and ConflatedChannel with one receiver are indistinguishable, so the general idea is the same.
I'm not aware of such concept as well. But how is this use-case resolved using Rx primitives when it naturally arises? Maybe with some combination of operators or with using special subject?
Yes, I think my statement is misleading. These issues arise in the broadcast channel, where semantic of "element consumption" is slightly different, so let's just ignore it :) In general, we have a conflict of |
@Khang-NT I'm a bit confused, why are you returning channel from the method, which asynchronously loads data once. You can change the signature of the method as
Does it make your application logic more understandable or simpler? Or you'd like to stick with channel solution? |
Personally I would like to use So to me, all use-cases involving
May I ask for use-case where |
@jcornaz What is closing the channel use-case? The way it was originally designed is that the channel is open while the system (or subsystem, or request, or some other activity) is working, and it keeps some number of open channels and |
Thanks for your very clear explanation @elizarov. Let's assume I have the following consumer: /**
* This consume the channel and keep something up-to-date according the states it receives.
* This function should not have to care what channel implementation is used.
*/
suspend fun ReceiveChannel<State>.keepSomethingUpToDate() {
consumeEach {
updateSomething(it) // here I update something, I don't care if I miss a state, because this is not deltas.
}
// here I know the state will not change any further.
// so I can safely release any resources and references I'm using.
// but is the think I'm updating up-to-date?
// If the channel is a rendez-vous, a buffered or a linked-list channel. Everything is fine.
// But, only in the case of a conflated channel, I may have missed an update to perform.
// So it means I have to care about the channel implementation?
// And how do I make sure that the think I am updating is actually up-to-date before releasing my resources?
} And now let's create a producer: fun produceStates() = produce(capacity = Channel.CONFLATED) {
// we send few states. they are not deltas, so it is fine to use a conflated channel, because any intermediate state can be safely lost.
send(state1)
send(state2)
// At some point I may know that the state will not change any further. How do I signal this to the consumer without taking the risk to miss the latest state?
delay(1, TimeUnit.SECONDS) // currently our only solution is to introduce an artificial delay (which is not good at all)
} So the use-case of closing a channel, is simply a way to signal the consumer that the state won't change any further. But that never means I want the consumer to miss the latest state. And all other channels finish to send their elements before actually send the close token. So why should it be different for Actually
|
To be (a little bit) more specific for the use-case of closing a conflated channel: Any time I have a long computation which wants to share intermediate results (because they are becoming more and more precise after each step of the calculation, for example). Conflated should be a good choice, because it doesn't matter if intermediate result are lost. At any point of time the latest available result is the best one. And the computation should not have to wait for the consumer. But, sooner or later the computation will terminate. and the latest sent element will be the actual final result. But the consumer may miss it with a Of course I may have a |
@jcornaz Now I get it. Thanks for detailed explanation. This "intermediate values" use-case is something I had not had in mind when designing |
Now the question is how to we distinguish those two use-cases in the names of classes or configuration options that we design for those uses. To recap, we have two conflicting use-case with respect to how they shall act on closing:
P.S. Spelling this out got me thinking that |
Yes, "close a channel with a value" would fulfill the use-case. However I still don't really understand where is the conflict between the two use cases. What if the rule would be the following:
You will notice that:
So if this rule is good for other channels in the use-case of data-flow computation. Why is it not good for |
@jcornaz If we change implementation of |
Ok. Thanks. I would be fine with both solution you propose. It is just in my opinion strange that Don't you think the current documentation of |
@jcornaz We'll need to adjust |
It's very confusing, because
close()
method:Is the
"close token"
treated equality with an element of Channel? I think it shouldn't be, then it shouldn't replace latest element of ConflatedChannel.The text was updated successfully, but these errors were encountered: