-
Notifications
You must be signed in to change notification settings - Fork 985
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
Coroutine commands that result in a Flow
can hang
#1837
Comments
Lettuce exposes Coroutines over a Reactive API. It suspends reading if there's not sufficient demand. This case looks pretty much it. Does the same happen if you move the |
Is the following adjustment what you are suggesting: Revised Example@OptIn(ExperimentalLettuceCoroutinesApi::class)
fun main() {
val i = AtomicInteger()
val client = RedisClient.create()
val connection = RedisURI.builder().apply {
withHost("localhost")
withPort(6379)
}.build().let {
client.connect(it)
}.coroutines()
val array = Array(128) { "111" }
repeat(1000) {
runBlocking {
connection.mget(
*array
).toList()
println("Iteration: ${i.getAndIncrement()}")
}
}
Thread.sleep(1000)
println("finish")
} The above sample still stalls randomly after some number of iterations, usually less than 50. This is surprising (to me, anyway), as I would expect a request for a specific set of elements in an |
Thanks. That's what I meant. I'm not sure how/whether Kotlin Coroutines optimize if results aren't consumed. However, fully consuming a result and running the same code in cycles should not cause a hanging application. I need to check what's happening. |
Flow
can hang
I had a look and it seems a bug in |
RedisPublisher's NO_DEMAND.request(…) while switching to DEMAND now calls onDataAvailable(…) on the currently active state to ensure data signalling if there's (still) demand. Previously, we called onDataAvailable(…) on the current NO_DEMAND state anticipating that if there's no data yet, then someone will read data from the channel and once it's there, we will be notified to emit it. Apparently, we can have data already in our buffer and so, upon requesting data, no one will notify us. Data was lingering in the publisher buffer and wasn't emitted. That caused downstream subscribers to hang indefinitely. Data emission (and command completion) can happen when responses for a command. The request size doesn't correlate with the response that we receive from Redis. Redis can respond with less, exactly or more items. In case we receive from Redis more items than were requested, we buffer these to comply with RS back pressure semantics. After emitting the last requested item to the subscription, we switch from DEMAND (READING) to NO_DEMAND. And the issue exactly starts there. We now call onDataAvailable(…) on the current state to ensure that when the state is DEMAND, that we will emit all data we have in our buffers.
RedisPublisher's NO_DEMAND.request(…) while switching to DEMAND now calls onDataAvailable(…) on the currently active state to ensure data signalling if there's (still) demand. Previously, we called onDataAvailable(…) on the current NO_DEMAND state anticipating that if there's no data yet, then someone will read data from the channel and once it's there, we will be notified to emit it. Apparently, we can have data already in our buffer and so, upon requesting data, no one will notify us. Data was lingering in the publisher buffer and wasn't emitted. That caused downstream subscribers to hang indefinitely. Data emission (and command completion) can happen when responses for a command. The request size doesn't correlate with the response that we receive from Redis. Redis can respond with less, exactly or more items. In case we receive from Redis more items than were requested, we buffer these to comply with RS back pressure semantics. After emitting the last requested item to the subscription, we switch from DEMAND (READING) to NO_DEMAND. And the issue exactly starts there. We now call onDataAvailable(…) on the current state to ensure that when the state is DEMAND, that we will emit all data we have in our buffers.
RedisPublisher's NO_DEMAND.request(…) while switching to DEMAND now calls onDataAvailable(…) on the currently active state to ensure data signalling if there's (still) demand. Previously, we called onDataAvailable(…) on the current NO_DEMAND state anticipating that if there's no data yet, then someone will read data from the channel and once it's there, we will be notified to emit it. Apparently, we can have data already in our buffer and so, upon requesting data, no one will notify us. Data was lingering in the publisher buffer and wasn't emitted. That caused downstream subscribers to hang indefinitely. Data emission (and command completion) can happen when responses for a command. The request size doesn't correlate with the response that we receive from Redis. Redis can respond with less, exactly or more items. In case we receive from Redis more items than were requested, we buffer these to comply with RS back pressure semantics. After emitting the last requested item to the subscription, we switch from DEMAND (READING) to NO_DEMAND. And the issue exactly starts there. We now call onDataAvailable(…) on the current state to ensure that when the state is DEMAND, that we will emit all data we have in our buffers.
Thanks so much for chasing this down! We'll grab the next release and give it a go... |
Hi, I was trying to run an mget on a program, stressing it with over 6000 requests per minute and after an hour, my program just stopped working and the last log that I have is: {'level':'DEBUG', 'message': '[channel=0x83706877, /127.0.0.1:55280 -> localhost/127.0.0.1:6379] writing command SubscriptionCommand [type=MGET, output=KeyValueListOutput [output=[], error='null'], commandType=io.lettuce.core.protocol.Command]', 'loggerName': 'io.lettuce.core.protocol.CommandEncoder:101', 'timestamp': '2021-10-25 11:57:58,521', 'thread': 'lettuce-nioEventLoop-4-1'} As you can see, I was locally running a docker container with my redis instance and connected to it by lettuce. |
Bug Report
We observed that when executing an
mget()
with a large set of keys against an empty Redis DB, the call would periodically stall. Further, we observed that this only occurred when the size of the requested set of keys was some multiple ofChannel.CHANNEL_DEFAULT_CAPACITY
(64). This occurs with some degree of randomness, but the attached test can usually trigger it within 15 - 50 calls tomget
.Current Behavior:
The following was collected via
kotlinx-coroutines-debug
, once a stalled request occurred:These are permanently
SUSPENDED
, and never resume.Input Code
Code Sample
Observed Behavior
The run will generally stall before 50 iterations are complete. A temporary fix appears to be applying an
UNLIMITED
buffer:The default behavior is
Channel.BUFFERED
, which triggers theCHANNEL_DEFAULT_CAPACITY
buffering in Kotlin'sPublisherAsFlow
Reactive adapter.Expected Behavior
mget()
should not permanently SUSPEND, and either return results or an error when the command completes.Environment
io.lettuce:lettuce-core:6.0.2.RELEASE
, we've also reproduced on6.1.4.RELEASE
.The text was updated successfully, but these errors were encountered: