Ok I've edited the whole question and produced two version of code which seems more interesting to show up the issue..
there are 2 functions here, one cause deadlock the other not..
the problem seems to be inside channel.asFlux() conversion but I don't understand what is the best practice to avoid such locks..
thank you,
Francesco
#Test
#Disabled("investigating..")
fun tt() = runBlocking<Unit>{
(0..3000).map { async {
//noDeadLock()
yesDeadLock()
}}.forEach { it.await()}
}
val thPool = newSingleThreadContext("single")
suspend fun yesDeadLock(){
withContext(Dispatchers.IO){ DEADLOCK
var channel = Channel<Int>(Channel.RENDEZVOUS)
val producer =
launch(newSingleThreadContext("nconte")){
while (isActive && !channel.isClosedForSend && !channel.isClosedForReceive ){
try{
channel.send(1)
}catch (t:Throwable){
}
}
}
withContext(Dispatchers.Default){
// withContext(thPool){ // <<-- with any other context, even a single thread, this long cause deadlocks
channel.asFlux()
.publishOn(Schedulers.elastic(),1)
.doFinally { producer.cancel() }
.limitRate(1)
.take(30)
.blockLast()
}
1
}
}
suspend fun noDeadLock(){
withContext(Dispatchers.Default){
var channel = Channel<Int>(Channel.RENDEZVOUS)
val producer =
launch(newSingleThreadContext("nconte")){
while (isActive && !channel.isClosedForSend && !channel.isClosedForReceive ){
try{
channel.send(1)
}catch (t:Throwable){
}
}
}
withContext(Dispatchers.IO){
channel.asFlux()
.publishOn(Schedulers.elastic(),1)
.doFinally { producer.cancel() }
.limitRate(1)
.take(30)
.blockLast()
}
1
}
}
Related
I want to use synchronized(object lock) at Kotlin, but idk how to use synchronized at Kotlin. I already search for the usage of synchronizing at Kotlin but ReentrantLock can't lock objects that I guess. please help me I am stuck with this 2 days ago.
override fun run() {
var active = false
while (true) {
while (queue.isEmpty()) {
if (!running) {
return
}
synchronized(this) {
try {
active = false
wait() //<< here's error
} catch (e: InterruptedException) {
LogUtils.getLogger()
.log(Level.SEVERE, "There was a exception with SQL")
LogUtils.logThrowable(e)
}
}
}
if (!active) {
con.refresh()
active = true
}
val rec = queue.poll()
con.updateSQL(rec.getType(), *rec.getArgs())
}
}
/**
* Adds new record to the queue, where it will be saved to the database.
*
* #param rec Record to save
*/
fun add(rec: Record) {
synchronized(this) {
queue.add(rec)
notifyAll() //<< here's too
}
}
/**
* Ends this saver's job, letting it save all remaining data.
*/
fun end() {
synchronized(this) {
running = false
notifyAll() //<< and here
}
}```
Well, the solution mentioned in Correctly implementing wait and notify in Kotlin will work here. Replacing wait() //<< here's error with (this as Object).work() and notifyAll() //<< and here with (this as Object).notifyAll() will lead to behavior that is identical to Java's one.
So I have a flow where I need it to emit a value from cache, but at the end it will make an API call to pull values in case there was nothing in cache (or refresh the value it has). I am trying this
override val data: Flow<List<Data>> = dataDao.getAllCachedData()
.onCompletion {
coroutineScope {
launch {
requestAndCacheDataOrEmitError()
}
}
}
.map { entities ->
entities
.map { it.toData() }
.filter { it !is Data.Unknown }
}
.filterNotNull()
.catch { emitRepositoryError(it) }
So the idea is that we emit the cache, and then make an API call to fetch new data regardless of the original mapping. But I do not want it blocking. For example, if we use this flow, I do not ever want the calling function to be blocked by the onCompletion.
I think the problem is that the onCompletion never runs. I set some breakpoints/logs and it never runs at all, even outside of the coroutineScope.
I don't quite understand the work you are doing but I think when you are collecting flow on a certain scope. You end the scope that flow will be put into onCompletion
var job : Job? = null
fun scan() {
job = viewModelScope.launch {
bigfileManager.bigFile.collect {
if (it is ResultOrProgress.Result) {
_bigFiles.value = it.result ?: emptyList()
} else {
_updateProgress.value = (it as ResultOrProgress.Progress).progress ?: 0
}
}
}
}
fun endScreen(){
job?.cancel()
}
I have a spring boot kotlin app that creates a web socket connection to another spring app, sends multiple "subscribe" messages, and then needs to wait for receipt of one response per subscription on the web socket connection. The number of subscriptions open at a given time could be up to a few thousand.
I've come up with a basic working solution using CompletableFuture and coroutines, as below. Is there a more idiomatic or concise way to do this task, or is this a fine solution? Any suggestions for improvement are appreciated.
// InputObject / ResponseObject are generic placeholders
fun getItems(inputObjects: List<InputObject>): List<ResponseObject> {
val ret: ConcurrentLinkedQueue<ResponseObject> = ConcurrentLinkedQueue()
// create a completable future for each input object
val subscriptions: MutableMap<String, CompletableFuture<ResponseObject>> = mutableMapOf()
inputObjects.forEach {
subscriptions[it.id] = CompletableFuture()
}
// create web socket client configured with a lambda handler to
// fulfill each subscription
// each responseObject.id matches one inputObject.id
val client = createWebSocketClient({
try {
val responseObject = objectMapper.readValue(it, ResponseObject::class.java)
subscriptions[responseObject.id]?.complete(responseObject)
} catch (e: Exception) {
logger.warn("Exception reading data: ${e.message}")
}
})
runBlocking {
coroutineScope {
for (item in inputObjects) {
launch {
// create and send a subscribe request
client.sendMessage(createSubscribe(item.id))
// wait for each future to complete
// uses CompletableFuture extension await() from kotlinx-coroutines-jdk8
val result = subscriptions[item.id]?.await()
if (result != null) {
ret.add(result)
}
}
}
}
}
client.close()
return ret.toList()
}
edit: I found a similar question: How to pass result as it comes using coroutines?
Which options makes the most sense?
fun getItems(inputObjects: List<InputObject>): List<ResponseObject> {
val subscriptions = ids.associateTo(mutableMapOf()) { it.id to CompletableFuture<ResponseObject>() }
val client = createWebSocketClient({
try {
val responseObject = objectMapper.readValue(it, ResponseObject::class.java)
subscriptions[responseObject.id]?.complete(responseObject)
} catch (e: Exception) {
logger.warn("Exception reading data: ${e.message}")
}
})
return runBlocking(Dispatchers.IO) {
inputObjects
.mapNotNull {
client.sendMessage(createSubscribe(item.id))
subscriptions[item.id]?.await()
}
}
}
Here is the code, it runs as expected, no exception
fun main() {
var mayBeEmptyString: String?
mayBeEmptyString = "1";
mayBeEmptyString?.let {
println("Inside let")
} ?: run {
throw RuntimeException("Inside run")
}
}
Output:
Inside let
And here is the code that I am not able to understand how it works:
fun main() {
var mayBeEmptyString: String?
mayBeEmptyString = "1";
mayBeEmptyString?.let {
// println("Inside let")
CompletableFuture.runAsync{ println("Inside let")}.join()
} ?: run {
throw RuntimeException("Inside run")
}
}
Output:
Inside let
Exception in thread "main" java.lang.RuntimeException: Inside run
at com.test.TestKt.main(test.kt:15)
at com.test.TestKt.main(test.kt)
Can anyone explain what is going on here? Thank you.
runAsync is meant for running a task that doesn't return a value, so you get a CompletableFuture<Void>, and attempting to read its value with get or join will give you null.
You then make this null result of join the result of your let block, which will cause your run block to be executed.
Because CompletableFuture.join() return null value cause
mayBeEmptyString?.let {
// println("Inside let")
CompletableFuture.runAsync{ println("Inside let")}.join()
}
will be null
and run { } will be executed
In the new project that I'm currently working on I have no RxJava dependency at all, because until now I didn't need that - coroutines solve threading problem pretty gracefully.
At this point I stumbled upon on a requirement to have a BehaviorSubject-alike behavior, where one can subscribe to a stream of data and receive the latest value upon subscription. As I've learned, Channels provide very similar behavior in Kotlin, so I decided to give them a try.
From this article I've learned, that ConflatedBroadcastChannel is the type of channel that mimics BehaviorSubject, so I declared following:
class ChannelSender {
val channel = ConflatedBroadcastChannel<String>()
fun sendToChannel(someString: String) {
GlobalScope.launch(Dispatchers.Main) { channel.send(someString) }
}
}
For listening to the channel I do this:
class ChannelListener(val channelSender: ChannelSender) {
fun listenToChannel() {
channelSender.channel.consumeEach { someString ->
if (someString == "A") foo.perform()
else bar.perform()
}
}
}
This works as expected, but at this point I'm having difficulties understanding how to unit test ChannelListener.
I've tried to find something related here, but none of example-channel-**.kt classes were helpful.
Any help, suggestion or correction related to my incorrect assumptions is appreciated. Thanks.
With the help of Alexey I could manage to end up having following code, which answers the question:
class ChannelListenerTest {
private val val channelSender: ChannelSender = mock()
private val sut = ChannelListener(channelSender)
private val broadcastChannel = ConflatedBroadcastChannel<String>()
private val timeLimit = 1_000L
private val endMarker = "end"
#Test
fun `some description here`() = runBlocking {
whenever(channelSender.channel).thenReturn(broadcastChannel)
val sender = launch(Dispatchers.Default) {
broadcastChannel.offer("A")
yield()
}
val receiver = launch(Dispatchers.Default) {
while (isActive) {
val i = waitForEvent()
if (i == endMarker) break
yield()
}
}
try {
withTimeout(timeLimit) {
sut.listenToChannel()
sender.join()
broadcastChannel.offer(endMarker) // last event to signal receivers termination
receiver.join()
}
verify(foo).perform()
} catch (e: CancellationException) {
println("Test timed out $e")
}
}
private suspend fun waitForEvent(): String =
with(broadcastChannel.openSubscription()) {
val value = receive()
cancel()
value
}
}