1

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.

beigirad
  • 4,986
  • 2
  • 29
  • 52
ITJscott
  • 522
  • 4
  • 17

0 Answers0