Skip to content

Commit

Permalink
Merge pull request #76 from Metatavu/bugfix-74-coroutine-timeout
Browse files Browse the repository at this point in the history
Fixed issue with Vert.x threads
  • Loading branch information
anttileppa authored Jul 17, 2024
2 parents 450d544 + 3c70bf0 commit bacc2b3
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import jakarta.ws.rs.core.Context
import jakarta.ws.rs.core.HttpHeaders
import jakarta.ws.rs.core.Response
import jakarta.ws.rs.core.SecurityContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.withTimeout
import org.eclipse.microprofile.config.inject.ConfigProperty
import org.eclipse.microprofile.jwt.JsonWebToken
import java.util.*
import io.vertx.kotlin.coroutines.dispatcher
import kotlinx.coroutines.async
import io.smallrye.mutiny.coroutines.asUni
import kotlinx.coroutines.*
import java.lang.Runnable
import kotlin.coroutines.CoroutineContext

/**
* Abstract base class for all API services
Expand Down Expand Up @@ -239,20 +237,6 @@ abstract class AbstractApi {
return createError(Response.Status.UNAUTHORIZED, message)
}

/**
* Wraps a block of code in a coroutine scope using a vertx dispatcher and a timeout
*
* @param block block of code to run
* @param requestTimeOut request timeout in milliseconds. Defaults to 10 seconds
* @return Uni
*/
@OptIn(ExperimentalCoroutinesApi::class)
protected fun <T> withCoroutineScope(block: suspend () -> T, requestTimeOut: Long = 10000L): Uni<T> {
return CoroutineScope(vertx.dispatcher())
.async { withTimeout(requestTimeOut) { block() } }
.asUni()
}

/**
* Constructs an error response
*
Expand Down Expand Up @@ -281,6 +265,37 @@ abstract class AbstractApi {
return "$entity with id $id not found"
}

/**
* Executes a block with coroutine scope
*
* @param requestTimeOut request timeout in milliseconds. Default is 10000
* @param block block to execute
* @return Uni
*/
@OptIn(ExperimentalCoroutinesApi::class)
protected fun <T> withCoroutineScope(requestTimeOut: Long = 10000L, block: suspend () -> T): Uni<T> {
val context = Vertx.currentContext()
val dispatcher = VertxCoroutineDispatcher(context)

return CoroutineScope(context = dispatcher)
.async {
withTimeout(requestTimeOut) {
block()
}
}
.asUni()
}

/**
* Custom vertx coroutine dispatcher that keeps the context stable during the execution
*/
private class VertxCoroutineDispatcher(private val vertxContext: io.vertx.core.Context): CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
vertxContext.runOnContext {
block.run()
}
}
}

companion object {
const val NOT_FOUND_MESSAGE = "Not found"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class TowablesApiImpl : TowablesApi, AbstractApi() {
@RolesAllowed(MANAGER_ROLE)
@WithTransaction
override fun createTowable(towable: fi.metatavu.vp.api.model.Towable): Uni<Response> =
withCoroutineScope({
withCoroutineScope {
val userId = loggedUserId ?: return@withCoroutineScope createUnauthorized(UNAUTHORIZED)

if (!vehicleController.isPlateNumberValid(towable.plateNumber)) {
Expand All @@ -42,7 +42,9 @@ class TowablesApiImpl : TowablesApi, AbstractApi() {
return@withCoroutineScope createBadRequest(INVALID_VIN)
}

if (!vehicleController.isPlateNumberUnique(towable.plateNumber)) return@withCoroutineScope createBadRequest(NOT_UNIQUE_PLATE_NUMBER)
if (!vehicleController.isPlateNumberUnique(towable.plateNumber)) return@withCoroutineScope createBadRequest(
NOT_UNIQUE_PLATE_NUMBER
)
if (!vehicleController.isVinUnique(towable.vin)) return@withCoroutineScope createBadRequest(NOT_UNIQUE_VIN)

val createdTruck = towableController.createTowable(
Expand All @@ -53,10 +55,10 @@ class TowablesApiImpl : TowablesApi, AbstractApi() {
userId = userId
)
createOk(towableTranslator.translate(createdTruck))
})
}

@RolesAllowed(DRIVER_ROLE, MANAGER_ROLE)
override fun findTowable(towableId: UUID): Uni<Response> = withCoroutineScope({
override fun findTowable(towableId: UUID): Uni<Response> = withCoroutineScope {
val truck = towableController.findTowable(towableId) ?: return@withCoroutineScope createNotFound(
createNotFoundMessage(
TOWABLE,
Expand All @@ -65,19 +67,19 @@ class TowablesApiImpl : TowablesApi, AbstractApi() {
)

createOk(towableTranslator.translate(truck))
})
}

@RolesAllowed(DRIVER_ROLE, MANAGER_ROLE)
override fun listTowables(plateNumber: String?, archived: Boolean?, first: Int?, max: Int?): Uni<Response> =
withCoroutineScope({
withCoroutineScope {
val (trucks, count) = towableController.listTowables(plateNumber, archived, first, max)
createOk(trucks.map { towableTranslator.translate(it) }, count)
})
}

@RolesAllowed(MANAGER_ROLE)
@WithTransaction
override fun updateTowable(towableId: UUID, towable: fi.metatavu.vp.api.model.Towable): Uni<Response> =
withCoroutineScope({
withCoroutineScope {
val userId = loggedUserId ?: return@withCoroutineScope createUnauthorized(UNAUTHORIZED)

if (!vehicleController.isPlateNumberValid(towable.plateNumber)) {
Expand Down Expand Up @@ -108,11 +110,11 @@ class TowablesApiImpl : TowablesApi, AbstractApi() {
val updatedTowable = towableController.updateTowable(existingTowable, towable, userId)

createOk(towableTranslator.translate(updatedTowable))
})
}

@RolesAllowed(MANAGER_ROLE)
@WithTransaction
override fun deleteTowable(towableId: UUID): Uni<Response> = withCoroutineScope({
override fun deleteTowable(towableId: UUID): Uni<Response> = withCoroutineScope {
loggedUserId ?: return@withCoroutineScope createUnauthorized(UNAUTHORIZED)
if (isProduction) return@withCoroutineScope createForbidden(FORBIDDEN)
val existingTowable = towableController.findTowable(towableId) ?: return@withCoroutineScope createNotFound(
Expand All @@ -129,5 +131,5 @@ class TowablesApiImpl : TowablesApi, AbstractApi() {

towableController.deleteTowable(existingTowable)
createNoContent()
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class PublicTrucksApiImpl: PublicTrucksApi, AbstractApi() {
vin: String?,
first: Int?,
max: Int?
): Uni<Response> = withCoroutineScope({
): Uni<Response> = withCoroutineScope {
val ( trucks, count ) = truckController.listTrucks(firstResult = first, maxResults = max, archived = null, plateNumber = null, vin = vin)
createOk(publicTruckTranslator.translate(trucks), count)
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {

@RolesAllowed(MANAGER_ROLE)
@WithTransaction
override fun createTruck(truck: fi.metatavu.vp.api.model.Truck): Uni<Response> = withCoroutineScope({
override fun createTruck(truck: fi.metatavu.vp.api.model.Truck): Uni<Response> = withCoroutineScope {
val userId = loggedUserId ?: return@withCoroutineScope createUnauthorized(UNAUTHORIZED)

if (!vehicleController.isPlateNumberValid(truck.plateNumber) || truck.vin.isEmpty()) {
Expand All @@ -87,14 +87,14 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {
userId = userId
)
createOk(truckTranslator.translate(createdTruck))
})
}

@RolesAllowed(DRIVER_ROLE, MANAGER_ROLE)
override fun findTruck(truckId: UUID): Uni<Response> = withCoroutineScope({
override fun findTruck(truckId: UUID): Uni<Response> = withCoroutineScope {
val truck = truckController.findTruck(truckId) ?: return@withCoroutineScope createNotFound(createNotFoundMessage(TRUCK, truckId))

createOk(truckTranslator.translate(truck))
})
}

@RolesAllowed(DRIVER_ROLE, MANAGER_ROLE)
override fun listTrucks(
Expand All @@ -104,7 +104,7 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {
sortDirection: SortOrder?,
first: Int?,
max: Int?
): Uni<Response> = withCoroutineScope({
): Uni<Response> = withCoroutineScope {
val ( trucks, count ) = truckController.listTrucks(
plateNumber = plateNumber,
archived = archived,
Expand All @@ -114,11 +114,11 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {
maxResults = max
)
createOk(trucks.map { truckTranslator.translate(it) }, count)
})
}

@RolesAllowed(MANAGER_ROLE)
@WithTransaction
override fun updateTruck(truckId: UUID, truck: fi.metatavu.vp.api.model.Truck): Uni<Response> = withCoroutineScope({
override fun updateTruck(truckId: UUID, truck: fi.metatavu.vp.api.model.Truck): Uni<Response> = withCoroutineScope {
val userId = loggedUserId ?: return@withCoroutineScope createUnauthorized(UNAUTHORIZED)

if (!vehicleController.isPlateNumberValid(truck.plateNumber) || truck.vin.isEmpty()) {
Expand All @@ -141,11 +141,11 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {
val updated = truckController.updateTruck(existingTruck, truck, userId)

createOk(truckTranslator.translate(updated))
})
}

@RolesAllowed(MANAGER_ROLE)
@WithTransaction
override fun deleteTruck(truckId: UUID): Uni<Response> = withCoroutineScope({
override fun deleteTruck(truckId: UUID): Uni<Response> = withCoroutineScope {
val existingTruck = truckController.findTruck(truckId) ?: return@withCoroutineScope createNotFound(createNotFoundMessage(TRUCK, truckId))
if (isProduction) return@withCoroutineScope createForbidden(FORBIDDEN)
val partOfVehicles = vehicleController.listVehicles(existingTruck)
Expand All @@ -154,11 +154,11 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {
}
truckController.deleteTruck(existingTruck)
createNoContent()
})
}

// Driver cards

override fun listTruckDriverCards(truckId: UUID): Uni<Response> = withCoroutineScope({
override fun listTruckDriverCards(truckId: UUID): Uni<Response> = withCoroutineScope {
if (loggedUserId == null && requestApiKey == null) return@withCoroutineScope createUnauthorized(UNAUTHORIZED)
if (requestApiKey != null && requestApiKey != apiKey) return@withCoroutineScope createForbidden(INVALID_API_KEY)
if (loggedUserId != null && !hasRealmRole(DRIVER_ROLE)) return@withCoroutineScope createForbidden(FORBIDDEN)
Expand All @@ -167,10 +167,10 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {

val (cards, count) = driverCardController.listDriverCards(truck)
createOk(driverCardTranslator.translate(cards), count)
})
}

@WithTransaction
override fun createTruckDriverCard(truckId: UUID, truckDriverCard: TruckDriverCard): Uni<Response> = withCoroutineScope({
override fun createTruckDriverCard(truckId: UUID, truckDriverCard: TruckDriverCard): Uni<Response> = withCoroutineScope {
if (requestApiKey != apiKey) return@withCoroutineScope createForbidden(INVALID_API_KEY)

val driverCardWithId = driverCardController.findDriverCard(truckDriverCard.id)
Expand All @@ -188,13 +188,13 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {
truck = truck
)
createOk(driverCardTranslator.translate(insertedCard))
})
}

@WithTransaction
override fun deleteTruckDriverCard(
truckId: UUID,
driverCardId: String
): Uni<Response> = withCoroutineScope({
): Uni<Response> = withCoroutineScope {
if (requestApiKey != apiKey) return@withCoroutineScope createForbidden(INVALID_API_KEY)
val truck = truckController.findTruck(truckId) ?: return@withCoroutineScope createNotFound(createNotFoundMessage(TRUCK, truckId))
val insertedDriverCard = driverCardController.findDriverCard(driverCardId) ?: return@withCoroutineScope createNotFound(createNotFoundMessage(DRIVER_CARD, driverCardId))
Expand All @@ -204,7 +204,7 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {

driverCardController.deleteDriverCard(insertedDriverCard)
createNoContent()
})
}

// Truck Speed endpoints

Expand All @@ -215,19 +215,19 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {
before: OffsetDateTime?,
first: Int?,
max: Int?
): Uni<Response> = withCoroutineScope({
): Uni<Response> = withCoroutineScope {
val truck = truckController.findTruck(truckId) ?: return@withCoroutineScope createNotFound(createNotFoundMessage(TRUCK, truckId))
val ( speeds, count ) = truckSpeedController.listTruckSpeeds(truck, after, before, first, max)
createOk(truckSpeedTranslator.translate(speeds), count)
})
}

@WithTransaction
override fun createTruckSpeed(truckId: UUID, truckSpeed: TruckSpeed): Uni<Response> = withCoroutineScope({
override fun createTruckSpeed(truckId: UUID, truckSpeed: TruckSpeed): Uni<Response> = withCoroutineScope {
if (requestApiKey != apiKey) return@withCoroutineScope createForbidden(INVALID_API_KEY)
val truck = truckController.findTruck(truckId) ?: return@withCoroutineScope createNotFound(createNotFoundMessage(TRUCK, truckId))
truckSpeedController.createTruckSpeed(truck, truckSpeed) ?: return@withCoroutineScope createAccepted(null)
createCreated()
})
}

// Truck location endpoints
@RolesAllowed(MANAGER_ROLE)
Expand All @@ -237,19 +237,19 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {
before: OffsetDateTime?,
first: Int?,
max: Int?
): Uni<Response> = withCoroutineScope({
): Uni<Response> = withCoroutineScope {
val truck = truckController.findTruck(truckId) ?: return@withCoroutineScope createNotFound(createNotFoundMessage(TRUCK, truckId))
val (locations, count) = truckLocationController.listTruckLocations(truck, after, before, first, max)
createOk(truckLocationTranslator.translate(locations), count)
})
}

@WithTransaction
override fun createTruckLocation(truckId: UUID, truckLocation: TruckLocation): Uni<Response> = withCoroutineScope({
override fun createTruckLocation(truckId: UUID, truckLocation: TruckLocation): Uni<Response> = withCoroutineScope {
if (requestApiKey != apiKey) return@withCoroutineScope createForbidden(INVALID_API_KEY)
val truck = truckController.findTruck(truckId) ?: return@withCoroutineScope createNotFound(createNotFoundMessage(TRUCK, truckId))
truckLocationController.createTruckLocation(truck, truckLocation) ?: return@withCoroutineScope createAccepted(null)
createCreated()
})
}

// Truck Drive State endpoints

Expand All @@ -262,7 +262,7 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {
before: OffsetDateTime?,
first: Int?,
max: Int?
) = withCoroutineScope({
) = withCoroutineScope {
val truck = truckController.findTruck(truckId) ?: return@withCoroutineScope createNotFound(createNotFoundMessage(TRUCK, truckId))
val (states, count) = truckDriveStateController.listDriveStates(
truck = truck,
Expand All @@ -274,14 +274,14 @@ class TrucksApiImpl: TrucksApi, AbstractApi() {
max = max
)
createOk(truckDriveStateTranslator.translate(states), count)
})
}

@WithTransaction
override fun createDriveState(truckId: UUID, truckDriveState: TruckDriveState): Uni<Response> = withCoroutineScope({
override fun createDriveState(truckId: UUID, truckDriveState: TruckDriveState): Uni<Response> = withCoroutineScope {
if (requestApiKey != apiKey) return@withCoroutineScope createForbidden(INVALID_API_KEY)
val truck = truckController.findTruck(truckId) ?: return@withCoroutineScope createNotFound(createNotFoundMessage(TRUCK, truckId))

truckDriveStateController.createDriveState(truck, truckDriveState) ?: return@withCoroutineScope createAccepted(null)
createCreated()
})
}
}
Loading

0 comments on commit bacc2b3

Please sign in to comment.