I am testing an event driven architecture in KTOR. My Core logic is held in a class that reacts to different Event types being emitted by a StateFlow. EventGenerators push Events into the StateFlow which are picked up by the Core.
However, when the Core attempts to respond to an ApplicationCall embedded in one of my Events I receive an ResponseAlreadySentException and I'm not sure why this would be the case. This does not happen if I bypass the StateFlow and call the Core class directly from the EventGenerator. I am not responding to ApplicationCalls anywhere else in my code, and have checked with breakpoints that the only .respond line is not being hit multiple times.
MyStateFlow class:
class MyStateFlow {
val state: StateFlow<CoreEvent>
get() = _state
private val _state = MutableStateFlow<CoreEvent>(CoreEvent.NothingEvent)
suspend fun update(event: CoreEvent) {
_state.value = event
}
}
My Core class:
class Core(
myStateFlow: MyStateFlow,
coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.IO
) {
init {
CoroutineScope(coroutineContext).launch {
myStateFlow.state.collect {
onEvent(it)
}
}
}
suspend fun onEvent(event: CoreEvent) {
when(event) {
is FooEvent {
event.call.respond(HttpStatusCode.OK, "bar")
}
...
}
}
}
One of my EventGenerators is a Route in my KTOR Application class:
get("/foo") {
myStateFlow.update(CoreEvent.FooEvent(call))
}
However, hitting /f00 in my browser returns either an ResponseAlreadySentException or an java.lang.UnsupportedOperationException with message: "Headers can no longer be set because response was already completed". The error response can flip between the two while I'm tinkering with different attempted solutions, but they seem to be saying the same thing: The call has already been responded to before I attempt to call call.respond(...).
If I change my Route instead to call the Core.onEvent() directly, hitting /foo returns "bar" in my browser as is the intended behaviour:
get("/foo") {
core.onEvent(CoreEvent.FooEvent(call))
}
For completeness, my dependency versions are:
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10"
implementation "io.ktor:ktor-server-netty:1.4.1"
Thank you in advanced for any insight you can offer.
Related
I'm drawing a blank on how to do this in project reactor with Spring Boot:
class BakerUserDetails(val bakerUser: Mono<BakerUser>): UserDetails {
override fun getPassword(): String {
TODO("Not yet implemented")
// return ???.password
}
override fun getUsername(): String {
TODO("Not yet implemented")
// return ???.username
}
}
How do I make this work? Do I just put bakerUser.block().password and bakerUser.block().username and all, or is there a better way to implement these methods?
Currently, I'm doing something like this but it seems strange:
private var _user: BakerUser? = null
private var user: BakerUser? = null
get() {
if(_user == null){
_user = bakerUser.block()
}
return _user
}
override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
return mutableSetOf(SimpleGrantedAuthority("USER"))
}
override fun getPassword(): String {
return user!!.password!!
}
im not well versed at Kotlin, but i can tell you that you should not pass in a Monoto the UserDetails object.
A Mono<T> is sort of like a future/promise. Which means that there is nothing in it. So if you want something out of it, you either block which means we wait, until there is something in it, or we subscribe, which basically means we wait async until there is something in it. Which can be bad. Think of it like starting a job on the side. What happens if you start a job and you quit the program, well the job would not be executed.
Or you do something threaded, and the program returns/exits, well main thread dies, all threads die, and nothing happend.
We usually in the reactive world talk about Publishers and Consumers. So a Flux/Mono is a Publisher and you then declare a pipelinefor what to happen when something is resolved. And to kick off the process the consumerneeds to subscribe to the producer.
Usually in a server world, this means that the webpage, that does the request, is the consumer and it subscribes to the server which in this case is the publisher.
So what im getting at, is that you, should almost never subscribe in your application, unless, your application is the one that starts the consumption. For instance you have a cron job in your server that consumes another server etc.
lets look at your problem:
You have not posted your code so im going to do some guesswork here, but im guessing you are getting a user from a database.
public Mono<BakerUserDetails> loadUserByUsername(String username) {
Mono<user> user = userRepository.findByUsername(username);
// Here we declare our pipline, flatMap will map one object to another async
Mono<BakerUserDetails> bakerUser = user.flatMap(user -> Mono.just(new BakerUserDetails(user));
return bakerUser;
}
i wrote this without a compiler from the top of my head.
So dont pass in the Mono<T> do your transformations using different operators like map or flatMap etc. And dont subscribe in your application unless your server is the final consumer.
I have read the set-based consistency validation blog and I want to validate through a dispatch interceptor. I follow the example, but I use reactive repository and it doesn't really work for me. I have tried both block and not block. with block it throws error, but without block it doesn't execute anything. here is my code.
class SubnetCommandInterceptor : MessageDispatchInterceptor<CommandMessage<*>> {
#Autowired
private lateinit var privateNetworkRepository: PrivateNetworkRepository
override fun handle(messages: List<CommandMessage<*>?>): BiFunction<Int, CommandMessage<*>, CommandMessage<*>> {
return BiFunction<Int, CommandMessage<*>, CommandMessage<*>> { index: Int?, command: CommandMessage<*> ->
if (CreateSubnetCommand::class.simpleName == (command.payloadType.simpleName)){
val interceptCommand = command.payload as CreateSubnetCommand
privateNetworkRepository
.findById(interceptCommand.privateNetworkId)
// ..some validation logic here ex.
// .filter { network -> network.isSubnetOverlap() }
.switchIfEmpty(Mono.error(IllegalArgumentException("Requested subnet is overlap with the previous subnet.")))
// .block() also doesn't work here it throws error
// block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-
}
command
}
}
}
Subscribing to a reactive repository inside a message dispatcher is not really recommended and might lead to weird behavior as underling ThreadLocal (used by Axox) is not adapted to be used in reactive programing
Instead, check out Axon's Reactive Extension and reactive interceptors section.
For example what you might do:
reactiveCommandGateway.registerDispatchInterceptor(
cmdMono -> cmdMono.flatMap(cmd->privateNetworkRepository
.findById(cmd.privateNetworkId))
.switchIfEmpty(
Mono.error(IllegalArgumentException("Requested subnet is overlap with the previous subnet."))
.then(cmdMono)));
I am trying call
override suspend fun getLoginResponse(loginRequest: LoginRequest) = flow {
emit(ApiResult.Loading)
networkCall {
loginService.postLoginResponse(loginRequest)
}.let { apiResult->
apiResult.isSuccessAndNotNull().letOnTrueOnSuspend {
(apiResult.getResult() as? LoginResponse)?.let {
emit(ApiResult.Success(it))
Timber.d(it.toString())
} ?: run { emit(ApiResult.Error(TypeCastException("unknown error.")))
Timber.d(TypeCastException("unknown error."))}
}
}
}.flowOn(Dispatchers.IO)
from my viewModel like this :
private fun loginResponse(email: String, password: String, device: String){
viewModelScope.launch {
try {
var loginRequest = LoginRequest(email, password, device)
loginResponseFromServer = loginRepository.getLoginResponse(loginRequest)
.asLiveData(viewModelScope.coroutineContext+Dispatchers.Default)
Timber.d(loginResponseFromServer.toString())
}
catch (e: NetworkErrorException){
validationError.value = "Network communication error!"
}
}
}
When I debug or run the code getLoginResponse not even calling. Is there anything I am missing?
First of all, getLoginResponse doesn't need to be a suspend function since it just returns a cold Flow. If you remove the suspend modifier, you won't need a coroutine to call it or convert it to LiveData.
Second, a LiveData that is built with .asLiveData() doesn't begin to collect the Flow (remains cold) until it first becomes active. This is in the docs for the function. It becomes active when it receives its first observer, but your code has not begun to observe it, which is why the code in your flow block is never called.
You also don't need to specify a different dispatcher for your LiveData. It doesn't matter which dispatcher you're collecting in since collecting it isn't blocking code.
However, LiveData isn't something that should be collected within a ViewModel. It's for UI to interact. The LiveData should be observed from the Fragment.
You need to move your catching of the network exception into your flow builder. The exception will not be thrown at the time of creating the Flow or LiveData, but rather at the time the request is being made (in the Flow's execution).
I'm not sure exactly how to rewrite your flow builder to properly catch because it has functions I haven't seen. Just a tip, but chaining together lots of scope functions into one statement makes code hard to read and reason about.
So to do this as LiveData, you can change your code as follows:
private fun loginResponse(email: String, password: String, device: String): LiveData<LoginResponse> {
val loginRequest = LoginRequest(email, password, device)
return loginRepository.getLoginResponse(loginRequest)
.asLiveData()
}
And then observe it in your Fragment.
However
LiveData and Flow don't really fit this use case, because you want to make a single request and get a single response. Your repository should just expose a suspend function that returns the response. Then your ViewModel can have a suspend function that just passes through the response by calling the repository's suspend function.
I'm trying to fetch some data from multiple locations to fill a recyclerView. I used to use callbacks, which worked fine, but need to refactor it to coroutines.
So i have a list of retrofit services and call each on of them parallerl. Then i can update the recyclerView with the onResponse callback. How can i achive this with coroutines.
I tried something like that, but the next call is fired after i got a response:
runblocking {
for (service in services) {
val response = async(Dispatchers.IO) {
service.getResponseAsync()
}
adapter.updateRecyclerView(response.await())
}
}
With another approach i had the problem that i was not able to get back on the main thread to update my ui as i was using launch and could not await the response:
runblocking {
services.foreach {
launch(Dispatcher.IO) {
val response = it.getResponseAsync()
}
withContext(Dispatcher.Main) {
adapter.updateRecyclerView(response)
}
}
}
I'm thankfull for every tip ;)
cheers patrick
Start coroutines with launch instead of runBlocking. The examples below assume you're launching from a context that uses Dispatchers.Main by default. If that's not the case, you could use launch(Dispatchers.Main) for these.
If you want to update your view every time any of the parallel actions returns, then move your UI update inside the coroutines that you're launching for each of the service items:
for (service in services) {
launch {
val response = withContext(Dispatchers.IO) { service.getResponseAsync() }
adapter.updateRecyclerView(response)
}
}
If you only need to update once all of them have returned, you can use awaitAll. Here, your updateRecyclerView function would have to be written to handle a list of responses instead of one at a time.
launch {
val responses = services.map { service ->
async(Dispatchers.IO) { service.getResponseAsync() }
}
adapter.updateRecyclerView(responses.awaitAll())
}
The await() call suspends the current coroutine and frees the current thread for being attached by other queued coroutines.
So when await() is called the current coroutine suspends till the response is received, and that's why for loop does not complete (goes to next iteration before completion of before request).
First and foremost you should not be using the runBlocking here, it is highly discouraged to be used in production evironment.
You should instead be using the ViewModel scope provided by android for structured concurrency (cancels the request if no longer needed like if lifecycle of activity is over).
You can use view model scope like this in activity or fragment viewModelOwner.viewModelScope.launch(/*Other dispatcher if needed*/) {} or make a coroutine scope yourself with a job attached which cancels itself on onDestroy.
For the problem the coroutine does not do parallel requests, you can launch multiple request without await (ing) on them inside the for loop.
And select them, using select expression https://kotlinlang.org/docs/reference/coroutines/select-expression.html#selecting-deferred-values
Example:
viewModelOwner.viewModelScope.launch {
val responses = mutableListOf<Deferred<TypeReturnedFromGetResponse>>()
for (service in services) {
async(Dispatchers.IO) {
service.getResponseAsync()
}.let(responses::add)
}
// adds which ever request is done first in oppose to awaiting for all then update
for (i in responses.indices) {
select<Unit> {
for (response in responses) {
response.onAwait {
adapter.updateRecyclerView(it)
}
}
}
}
}
PS: Using this method looks ugly but will update the adapter as soon as whichever request is first resolved, instead of awaiting for each and every request and then updating the items in it.
I am trying to make a class that would take incoming user events, process them and then pass the result to whoever subscribed to it:
class EventProcessor
{
val flux: Flux<Result>
fun onUserEvent1(e : Event)
{
val result = process(e)
// Notify flux that I have a new result
}
fun onUserEvent2(e : Event)
{
val result = process(e)
// Notify flux that I have a new result
}
fun process(e : Event): Result
{
...
}
}
Then the client code can subscribe to EventProcessor::flux and get notified each time a user event has been successfully processed.
However, I do not know how to do this. I tried to construct the flux with the Flux::generate function like this:
class EventProcessor
{
private var sink: SynchronousSink<Result>? = null
val flux: Flux<Result> = Flux.generate{ sink = it }
fun onUserEvent1(e : Event)
{
val result = process(e)
sink?.next(result)
}
fun onUserEvent2(e : Event)
{
val result = process(e)
sink?.next(result)
}
....
}
But this does not work, since I am supposed to immediately call next on the SynchronousSink<Result> passed to me in Flux::generate. I cannot store the sink as in the example:
reactor.core.Exceptions$ErrorCallbackNotImplemented:
java.lang.IllegalStateException: The generator didn't call any of the
SynchronousSink method
I was also thinking about the Flux::merge and Flux::concat methods, but these are static and they create a new Flux. I just want to push things into the existing flux, such that whoever holds it, gets notified.
Based on my limited understanding of the reactive types, this is supposed to be a common use case. Yet I find it very difficult to actually implement it. This brings me to a suspicion that I am missing something crucial or that I am using the library in an odd way, in which it was not intended to be used. If this is the case, any advice is warmly welcome.