Skip to content
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

Implement a Vert.x Web Router that provides support for suspending functions #253

Merged
merged 6 commits into from
Oct 24, 2023

Conversation

tsegismont
Copy link
Contributor

See #194

This router implements CoroutineScope to define a scope for new coroutines. Typically, this is the scope of a CoroutineVerticle.

…nctions

See vert-x3#194

This router implements CoroutineScope to define a scope for new coroutines.
Typically, this is the scope of a CoroutineVerticle.

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
@tsegismont
Copy link
Contributor Author

@bbyk would you mind taking a look at this one?

@tsegismont tsegismont requested review from jponge and vietj October 18, 2023 16:24
@sanyarnd
Copy link

I can add more context from our experience with coroutines and point out possible challenges.

  1. Tracing is hard, especially if you bridge vertx to coroutines and back, e.g. vertx -> cohandler -> Dispatchers.IO -> HTTP/SQL client -> back to vertx. It's somewhat confusing, it is hard make right and it has limitations (for example, ctx.fail).
  2. Logging is also somewhat hard, because you need correlation traces dumped, but logging libraries are colorless, so they don't know about coroutines. It can be solved via custom ThreadContextElement (e.g. MDCThreadContext), but Vertx uses it's own variant of MDC (local data) and you need to connect them together.

For example, we're wrapping all client calls in "proxies" (transparent for user via custom CoroutineWebClient class with suspend functions):

suspend fun <T> onVertxContext(vertx: Vertx, block: suspend () -> T): T {
    val ctx = coroutineContext.tracingContext()
    val vertxCtx = when (val currentContext = Vertx.currentContext()) {
        null -> (vertx as VertxInternal).getOrCreateContext()
        else -> currentContext
    }.dispatcher()

    return withContext(vertxCtx) {
        VertxTracingUtils.setContext(ctx)
        try {
            block()
        } finally {
            VertxTracingUtils.clearContext()
        }
    }
}

Otherwise vertx HTTP/SQL client will not propagate context.

There's VertxTracer interface that provides receive/send methods, but it's, again, colorless, and the only way to pass traces is local data (not MDC):

    @Override
    public <R> Span sendRequest(
        final Context context,
        final SpanKind kind,
        final TracingPolicy policy,
        final R request,
        final String operation,
        final BiConsumer<String, String> headers,
        final TagExtractor<R> tagExtractor
    ) {
        if (TracingPolicy.IGNORE.equals(policy) || request == null) {
            return null;
        }

        io.opentelemetry.context.Context propagatedContext = context.getLocal(ACTIVE_CONTEXT);

        // ...

Point is, adding coroutine support is not so easy as adding router, and if done w/o preparations it'll make code non-compatible with other modules :(
Coroutines like a plague, you need to color a lot of code, otherwise they don't work as expected.

@bbyk
Copy link

bbyk commented Oct 19, 2023

@tsegismont I am curious if you considered going extension route. Basically, for the coHandler you can do something like

class CoRoute(scope: CoroutineScope) : CoroutineScope by scope {
  fun Route.coHandler(fn: suspend (RoutingContext) -> Unit) = handler {
      launch {
          try {
              fn(it)
          } catch (e: Exception) {
              it.fail(e)
          }
      }
  }
}

So then you can use with block to have both receivers e.g.

val scope = CoroutineScope(vertx.orCreateContext.dispatcher())
with(CoRoute(scope)) {
  Router.router(vertx).apply {
    get("/somePath").coHandler({ ... })
    get("/someOtherPath").coHandler({ ... })
    ...// more routes
  }
}

Could be less code this way

@tsegismont
Copy link
Contributor Author

@bbyk thanks for the tip, I thought about this then:

fun withCoroutineSupport(
  vertx: Vertx,
  scope: CoroutineScope,
  block: CoroutineVertxSupport.() -> Unit
) {
  val coroutineSupport = object : CoroutineVertxSupport {
    override fun getVertx() = vertx
    override val coroutineContext = scope.coroutineContext
  }
  with(coroutineSupport) { block() }
}

interface CoroutineVertxSupport : CoroutineScope {

  fun getVertx(): Vertx

  fun Router.coErrorHandler(statusCode: Int, errorHandler: suspend (RoutingContext) -> Unit): Router =
    errorHandler(statusCode) {
      launch {
        try {
          errorHandler(it)
        } catch (e: Exception) {
          it.fail(e)
        }
      }
  }

  fun Route.coHandler(fn: suspend (RoutingContext) -> Unit): Route = handler {
    launch {
      try {
        fn(it)
      } catch (e: Exception) {
        it.fail(e)
      }
    }
  }

  fun Route.coFailureHandler(fn: suspend (RoutingContext) -> Unit): Route = failureHandler {
    launch {
      try {
        fn(it)
      } catch (e: Exception) {
        it.fail(e)
      }
    }
  }

  fun <T> Route.coRespond(fn: suspend (RoutingContext) -> T): Route = respond {
    val vertx = it.vertx() as VertxInternal
    val promise = vertx.promise<T>()
    launch {
      try {
        promise.complete(fn.invoke(it))
      } catch (e: Exception) {
        promise.fail(e)
      }
    }
    promise.future()
  }
}

We can make CoroutineVerticle implement CoroutineVertxSupport:

abstract class CoroutineVerticle : Verticle, CoroutineVertxSupport

And we still get a good UX when implementing the webapp:

class TestVerticle : CoroutineVerticle() {

  @Volatile
  var actualPort: Int = 0

  override suspend fun start() {
    val router = Router.router(vertx)
    router.coErrorHandler(404) { rc ->
      delay(100)
      rc.response().setStatusCode(404).end("Too bad...")
    }
    router.route().handler { rc ->
      rc.put("capturedContext", ContextInternal.current())
      rc.next()
    }
    router.get("/suspendingHandler").coHandler { rc ->
      delay(100)
      val current = ContextInternal.current()
      if (current.isDuplicate && current == rc.get("capturedContext")) {
        rc.end()
      } else {
        rc.fail(500)
      }
    }
    router.get("/suspendingRespond").coRespond { rc ->
      delay(100)
      val current = ContextInternal.current()
      if (!current.isDuplicate || current != rc.get("capturedContext")) {
        throw RuntimeException()
      }
      "foobar"
    }
    router.get("/suspendingFailureHandler").coHandler { it.fail(RuntimeException()) }.coFailureHandler { rc ->
      delay(100)
      val current = ContextInternal.current()
      if (current.isDuplicate && current == rc.get("capturedContext")) {
        rc.end("baz")
      } else {
        rc.response().setStatusCode(500).end()
      }
    }
    val externalRouteHandler = ExternalRouteHandler()
    router.get("/externalRoute").coHandler { externalRouteHandler.handle(it) }
    router.route("/parent/*").subRouter(createSubRouter(vertx, this))
    val httpServer = vertx.createHttpServer()
      .requestHandler(router)
      .listen(0)
      .await()
    actualPort = httpServer.actualPort()
  }
}

class ExternalRouteHandler {
  suspend fun handle(rc: RoutingContext) {
    delay(100)
    val current = ContextInternal.current()
    if (!current.isDuplicate || current != rc.get("capturedContext")) {
      rc.fail(500)
    }
    rc.end("someone kicked the ball")
  }
}

suspend fun createSubRouter(vertx: Vertx, scope: CoroutineScope): Router {
  val router = Router.router(vertx)
  withCoroutineSupport(vertx, scope) {
    router.get("/child").coRespond { rc ->
      delay(100)
      val current = ContextInternal.current()
      if (!current.isDuplicate || current != rc.get("capturedContext")) {
        throw RuntimeException()
      }
      "Hello, IT"
    }
  }
  return router
}

It's very similar to your proposal except that:

  • inside the VertxCoroutineSupport interface, we could add all the extensions we need (e.g. EventBus)
  • nothing special is needed inside a CoroutineVerticle

@tsegismont
Copy link
Contributor Author

Otherwise vertx HTTP/SQL client will not propagate context.

@sanyarnd this may be solved #234

@bbyk
Copy link

bbyk commented Oct 20, 2023

@tsegismont It is great! A couple of minor notes:

  1. I know I used e: Exception myself in my excerpt but maybe it's safer to use
      } catch (e: Throwable) {
        it.fail(e)
      }
  1. For coRespond you might be able to re-use the vertxFuture fn from VertxCoroutine.kt ( off-topic: I like the ergonomics of https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/future.html more and having similarly modeled - extension on CoroutineScope plus a start param - API instead of / along with vertxFuture would be great )

@bbyk
Copy link

bbyk commented Oct 20, 2023

@tsegismont

  1. In the same vein, you might want to consider withCoroutineSupport to be an extension on CoroutineScope e.g.
fun CoroutineScope.withCoroutineSupport(
  vertx: Vertx,
  block: CoroutineVertxSupport.() -> Unit
) {
  val coroutineSupport = object : CoroutineVertxSupport {
    override fun getVertx() = vertx
    override val coroutineContext = scope.coroutineContext
  }
  with(coroutineSupport) { block() }
}

so that you would just have

suspend fun CoroutineScope.createSubRouter(vertx: Vertx): Router {
  val router = Router.router(vertx)
  withCoroutineSupport(vertx) {
    router.get("/child").coRespond { rc ->
      delay(100)
      val current = ContextInternal.current()
      if (!current.isDuplicate || current != rc.get("capturedContext")) {
        throw RuntimeException()
      }
      "Hello, IT"
    }
  }
  return router
}

...
router.route("/parent/*").subRouter(createSubRouter(vertx))

@tsegismont
Copy link
Contributor Author

tsegismont commented Oct 20, 2023

@bbyk I tried your proposal with EventBus and it works quite well too:

fun CoroutineScope.withCoroutineEventBus(
  vertx: Vertx,
  block: CoroutineEventBusSupport.() -> Unit
) {
  val coroutineSupport = object : CoroutineEventBusSupport {
    override fun getVertx() = vertx
    override val coroutineContext = this@withCoroutineEventBus.coroutineContext
  }
  with(coroutineSupport) { block() }
}


interface CoroutineEventBusSupport : CoroutineScope {

  fun getVertx(): Vertx

  fun <T> MessageConsumer<T>.coHandler(block: suspend (Message<T>) -> Unit): MessageConsumer<T> = handler {
    launch {
      try {
        block(it)
      } catch (e: Exception) {
        it.fail(RECIPIENT_FAILURE.toInt(), e.message)
      }
    }
  }

  fun <T> EventBus.coConsumer(address: String, block: suspend (Message<T>) -> Unit): MessageConsumer<T> =
    consumer<T>(address).coHandler(block)
}

This is a quite compact form compared to the existing code

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
@tsegismont
Copy link
Contributor Author

So in the second iteration, I pushed this:

fun CoroutineScope.coroutineRouter(
  vertx: Vertx,
  block: CoroutineRouterSupport.() -> Unit
) = with(object : CoroutineRouterSupport {
  override fun getVertx() = vertx
  override val coroutineContext = this@coroutineRouter.coroutineContext
}) { block() }

/**
 * Adds support for suspending function in the Vert.x Web [Router].
 *
 * Objects of this type implement [CoroutineScope] to define a scope for new coroutines.
 * Typically, this is the scope of [CoroutineVerticle].
 */
interface CoroutineRouterSupport : CoroutineScope {

  /**
   * The [Vertx] instance related to this scope.
   */
  fun getVertx(): Vertx

  /**
   * The [CoroutineDispatcher] used to dispatch new coroutines.
   *
   * By default, this is the [Vertx.dispatcher].
   */
  fun getDispatcher(): CoroutineDispatcher = getVertx().dispatcher()

  /**
   * Similar to [Router.errorHandler] but using a suspended [errorHandler].
   */
  fun Router.coErrorHandler(statusCode: Int, errorHandler: suspend (RoutingContext) -> Unit): Router =
    errorHandler(statusCode) {
      launch(getDispatcher()) {
        try {
          errorHandler(it)
        } catch (t: Throwable) {
          it.fail(t)
        }
      }
    }

  /**
   * Similar to [Route.handler] but using a suspended [requestHandler].
   */
  fun Route.coHandler(requestHandler: suspend (RoutingContext) -> Unit): Route = handler {
    launch(getDispatcher()) {
      try {
        requestHandler(it)
      } catch (t: Throwable) {
        it.fail(t)
      }
    }
  }

  /**
   * Similar to [Route.failureHandler] but using a suspended [failureHandler].
   */
  fun Route.coFailureHandler(failureHandler: suspend (RoutingContext) -> Unit): Route = failureHandler {
    launch(getDispatcher()) {
      try {
        failureHandler(it)
      } catch (t: Throwable) {
        it.fail(t)
      }
    }
  }

  /**
   * Similar to [Route.respond] but using a suspended [function].
   */
  fun <T> Route.coRespond(function: suspend (RoutingContext) -> T): Route = respond {
    val vertx = it.vertx() as VertxInternal
    val promise = vertx.promise<T>()
    launch(getDispatcher()) {
      try {
        promise.complete(function.invoke(it))
      } catch (t: Throwable) {
        it.fail(t)
      }
    }
    promise.future()
  }
}

Then, a user only has to add the CoroutineRouterSupport to their CoroutineVerticle to get the extensions:

class TestVerticle : CoroutineVerticle(), CoroutineRouterSupport

By default, CoroutineRouterSupport uses the vertx.dispatcher() when lauching coroutines. And, in order to get more control, the getDispatcher can be overriden.

@sanyarnd
Copy link

sanyarnd commented Oct 20, 2023

Otherwise vertx HTTP/SQL client will not propagate context.

@sanyarnd this may be solved #234

thanks, that looks exactly what I've been waiting for -- a bridge for vertx and coroutine on the library level, though I can't see changes for context restoration (eg ThreadContextElement)

For example:

Inbound request (Vertx context/thread)
  => switch to computation sensetive task via Dispatchers.IO or ExecutorService
     => Vertx HTTP/SQL client call (need to restore context here and propagate it)
  => continue on vertx thread

I suppose to solve such cases one still should implement its own helper methods, right?

@bbyk
Copy link

bbyk commented Oct 20, 2023

I wonder if it makes sense to double down on some more idiomatic Kotlin coroutine patterns here. For example, with the following code:

data class VertxContext(val context: Context) : AbstractCoroutineContextElement(VertxContext) {
    companion object Key : CoroutineContext.Key<VertxContext>
}

fun CoroutineScope.vertxContext() = coroutineContext[VertxContext] ?: error("Current context doesn't contain VertxContext in it: $this")
fun CoroutineScope.vertx() = vertxContext().owner()

fun Context.coroutineContext(): CoroutineContext = dispatcher() + VertxContext(this)

you can basically drop #getVertx, #getDispatcher from CoroutineRouterSupport. For example for coHandler you no longer need to to call launch(getDispatcher()) { just launch { to use the current dispatcher which can be overridden with more idiomatic withContext pattern.

And coroutineRouter can be like this

fun CoroutineScope.coroutineRouter(
  context: CoroutineContext = EmptyCoroutineContext,
  block: CoroutineRouterSupport.(vertx: Vertx) -> Unit
) {
  val newContext = coroutineContext.newCoroutineContext(context)
  val vertx = newContext.vertx()
  with(object : CoroutineRouterSupport {
    override val coroutineContext = newContext
  }) { block(vertx) }
}

If your CoroutineScope is not that off of Vertx, you can call the function like so

coroutineRouter(vertx.orCreateContext.coroutineContext()) {
  router.get("/child").coRespond { rc ->

Or if you want to run on different dispatcher, you can always do this

withContext(differentDispatcher) {
 coroutineRouter {
      router.get("/child").coRespond { rc ->

@bbyk
Copy link

bbyk commented Oct 21, 2023

I might be wrong on this one:

For example for coHandler you no longer need to to call launch(getDispatcher()) { just launch { to use the current dispatcher which can be overridden with more idiomatic withContext pattern.

It seems that the coHandler would need to explicitly pick up the current Vertx context in which the handler is called because it's going to be that DuplicatedContext for just that http call and the coroutine should stay on it

  fun Route.coHandler(requestHandler: suspend (RoutingContext) -> Unit): Route = handler {
    launch(Vertx.currentContext().coroutineContext()) {

@tsegismont
Copy link
Contributor Author

I suppose to solve such cases one still should implement its own helper methods, right?

@sanyarnd Possibly, yes. Would you mind sharing with me a small reproducer on a public Git repo? So I fully understand the case you're talking about. Thanks

@tsegismont
Copy link
Contributor Author

I might be wrong on this one:

For example for coHandler you no longer need to to call launch(getDispatcher()) { just launch { to use the current dispatcher which can be overridden with more idiomatic withContext pattern.

It seems that the coHandler would need to explicitly pick up the current Vertx context in which the handler is called because it's going to be that DuplicatedContext for just that http call and the coroutine should stay on it

  fun Route.coHandler(requestHandler: suspend (RoutingContext) -> Unit): Route = handler {
    launch(Vertx.currentContext().coroutineContext()) {

It might not be a problem, thanks to the changes made recently. A dispatcher created for a base context shall take into account duplicated context when evaluating if dispatch is needed and when dispatching.

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
@tsegismont tsegismont removed request for jponge and vietj October 23, 2023 16:33
@tsegismont tsegismont marked this pull request as ready for review October 23, 2023 16:34
@tsegismont tsegismont requested a review from bbyk October 23, 2023 16:34
@tsegismont
Copy link
Contributor Author

tsegismont commented Oct 23, 2023

@bbyk I've given your proposal some thought today.

I agree, we can drop the getVertx and getDispatcher methods. Instead, we shall let the user provide a CoroutineContext (empty by default) when they invoke coHandler and others. That's how launch is designed anyway.

I believe your idea of creating a coroutine context element key for the Vert.x context is a good one. However, it can be done in another pull-request and is not required to implement a Vert.x Web Router that provides support for suspending functions.

You're welcome to make another review if you have time for this. I'm going to merge the PR tomorrow so that we can get this enhancement in Vert.x 4.5

Thanks again for your very good feedback. It is much appreciated.

@bbyk
Copy link

bbyk commented Oct 23, 2023

@tsegismont Thank you!

I still think the correct implementation might be to always launch on current context to cover all corner cases. Please see my comment on the PR: https://github.com/vert-x3/vertx-lang-kotlin/pull/253/files#r1369034860 . For example, delay(100) only works because Vertx#setTimer captures current context but any functionality that solely relies on the current dispatcher instead will not end up on the DuplicatedContext

@bbyk
Copy link

bbyk commented Oct 23, 2023

You're welcome to make another review if you have time for this. I'm going to merge the PR tomorrow so that we can get this enhancement in Vert.x 4.5

@tsegismont Sorry for the off-topic: please check out #256 for the upcoming release

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
The default behavior should be to use the coroutine dispatcher of the Vert.x context.
Users can override this by providing additional context elements.

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
context: CoroutineContext = EmptyCoroutineContext,
requestHandler: suspend (RoutingContext) -> Unit
): Route = handler {
launch(it.vertx().dispatcher() + context) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea! You probably want to use newCoroutineContext instead of plus as it does a little bit more corner case analysis. As far as I understand, plus could be used directly when you plus something more specific as in + CoroutineName("...") and if it's a generic context then it's probably best to delegate to newCoroutineContext

launch(it.vertx().dispatcher().newCoroutineContext(context)) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newCoroutineContext will be called by launch

Copy link

@bbyk bbyk Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. You're right. Even if the context arg of coHandler had, say, CopyableThreadContextElement the plus would get the element into the resulting context without copying but newCoroutineContext in launch will copy from the element since it'd be on the addedContext side of the fold.

@tsegismont tsegismont merged commit e841975 into vert-x3:master Oct 24, 2023
3 checks passed
@tsegismont tsegismont deleted the coroutine-router branch October 24, 2023 16:47
tsegismont added a commit that referenced this pull request Oct 24, 2023
…nctions

Backported from #253

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
tsegismont added a commit to tsegismont/vertx-lang-kotlin that referenced this pull request Oct 24, 2023
Follows implementation of coroutine router in vert-x3#253

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
tsegismont added a commit that referenced this pull request Oct 24, 2023
Follows implementation of coroutine router in #253

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
tsegismont added a commit that referenced this pull request Oct 24, 2023
Follows implementation of coroutine router in #253

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
ShreckYe added a commit to huanshankeji/FrameworkBenchmarks that referenced this pull request Oct 29, 2024
…-kotlin#253 / vert-x3/vertx-lang-kotlin@e841975

There is no noticeable performance degradation in the "plaintext" and "JSON serialization" tests.
NateBrady23 pushed a commit to TechEmpower/FrameworkBenchmarks that referenced this pull request Nov 4, 2024
…-kotlin-coroutines" (#9374)

* Revamp the "vertx-web-kotlinx" portion project

Changes:
1. bump the Gradle versions and the dependency versions to the latest
1. bump the JVM version to 21 with the new `jvmToolchain` DSL
1. resolve deprecations
1. update the dockerfile to use the `installDist` Gradle task so the time for archiving and unarchiving is saved

* Run the revamped benchmark

There are no build or runtime errors. Single query performance seems to have been improved by 2% with the bumped versions (I think most likely due to Java 21). Vagrant reduces performance by about 20% compared to running directly with Docker.

* Enable io_uring and run the benchmark

The single query performance is improved by 5% - 10%.

* Try fixing the performance issues of the "vertx-web-kotlinx" portion in the "single query" and "JSON serialization" tests in the [Continuous Benchmarking results](https://tfb-status.techempower.com/) by using static kotlinx.serialization serializers

The "vertx-web-kotlinx" results in the Continuous Benchmarking results are much lower than those of the "vertx-web-kotlin-coroutines" portion. See [the latest results](https://www.techempower.com/benchmarks/#section=test&runid=592cab59-a9db-463b-a9c9-33d2f9484e92&hw=ph&test=db) for example.

Looking at the "single query" results, I first suspected that it was caused by there being not enough memory for the JVM runtime, so I added some logging code that prints the memory usage using `Runtime.totalMemory` and `Runtime.maxMemory`. It showed that there was about 7 GB max memory available during the benchmark runs, and the program only used 400 MB to 1 GB. I then tried allocating a 4 GB array during the run to ensure that the memory was usable and it worked with no problem.

Then looking at the "JSON serialization" results again, I saw that "vertx-web-kotlinx" performs a lot worse in this test too, and decided that this is more likely to be the bottleneck. Therefore, the static serializers are provided explicitly and the performance is improved slightly as tested on my machine. (Also, see commit 315b4e3 for an attempt before.) I then copied the "JSON serialization" test code from "vertx-web-kotlin-coroutines" and ran the benchmark to see if there were other factors, such as project configuration differences, affecting the performance, and the answer was no. On my machine, the "JSON serialization" performance of "vertx-web-kotlinx" is about 80% - 85% of that of "vertx-web-kotlin-coroutines". And I think the bottleneck possibly lies in kotlinx.serialization serializing an object to a byte array first and then copying it to a Vert.x buffer.

Remove the broken tag in "vertx-web-kotlin-coroutines" BTW, which was added in commit e53e026, for the benchmark runs without problems now as I tested.

* Update README.md correspondingly

* Add `--no-daemon` to the Gradle command in the Dockerfiles

* Update the "Connection reset" exception to the io_uring one that's ignored in logging

* Use `encodeToBufferedSink` in "kotlinx-serialization-json-okio" with a custom-implemented `VertxBufferSink` in JSON serialization

The "JSON serialization" performance is on par with using `io.vertx.core.json.Json.encodeToBuffer` as tested on my machine after this change.

* Replace Okio with kotlinx-io and remove unneeded code

The "JSON serialization" performance seems to be slightly less.

* Rename 2 classes to clarify

* Use to the new Vert.x coroutine APIs introduced in vert-x3/vertx-lang-kotlin#253 / vert-x3/vertx-lang-kotlin@e841975

There is no noticeable performance degradation in the "plaintext" and "JSON serialization" tests.

* Simply the code introduced in the previous commit by making `MainVerticle` implement `CoroutineRouterSupport`

I didn't go through [the docs](https://vertx.io/docs/vertx-lang-kotlin-coroutines/kotlin/#_vert_x_web) thoroughly before implementing this.

* Revamp the "vertx-web-kotlin-coroutines" portion project too following the changes made to the "vertx-web-kotlinx" portion

The "gradlew" script somehow had incorrect access permissions and is fixed by bumping the Gradle wrapper.

To keep the dependencies consistent with the "vert-web" portion, some dependencies are not updated to the latest versions.

Remove 2 useless `COPY`s in "vertx-web-kotlin-coroutines-postgres.dockerfile" BTW.

* Remove unneeded and incorrect comments

Wrapping something as a Vert.x `Buffer` by implementing the `Buffer` interface is not viable because `BufferImpl` contains casts from a `Buffer` to a `BufferImpl`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants