Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions explore/app/src/main/scala/explore/targets/TargetSource.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package explore.targets
import cats.Order
import cats.data.NonEmptyList
import cats.effect.Async
import cats.effect.std.Random
import cats.syntax.all.*
import eu.timepit.refined.types.string.NonEmptyString
import explore.common.SimbadSearch
Expand Down Expand Up @@ -40,7 +41,8 @@ object TargetSource:

override def toString: String = programId.toString

case class FromCatalog[F[_]: Async: Logger](catalogName: CatalogName) extends TargetSource[F]:
case class FromCatalog[F[_]: Async: Random: Logger](catalogName: CatalogName)
extends TargetSource[F]:
val name: String = Enumerated[CatalogName].tag(catalogName).capitalize

val existing: Boolean = false
Expand Down Expand Up @@ -97,12 +99,12 @@ object TargetSource:

override def toString: String = catalogName.toString

def forAllCatalogs[F[_]: Async: Logger]: NonEmptyList[TargetSource[F]] =
def forAllCatalogs[F[_]: Async: Random: Logger]: NonEmptyList[TargetSource[F]] =
NonEmptyList.fromListUnsafe(
Enumerated[CatalogName].all.map(source => TargetSource.FromCatalog(source))
)

def forAllSiderealCatalogs[F[_]: Async: Logger]: NonEmptyList[TargetSource[F]] =
def forAllSiderealCatalogs[F[_]: Async: Random: Logger]: NonEmptyList[TargetSource[F]] =
NonEmptyList.of(TargetSource.FromCatalog(CatalogName.Simbad))

// TODO Test
Expand Down
35 changes: 0 additions & 35 deletions explore/common/src/main/scala/explore/common/RetryHelpers.scala

This file was deleted.

26 changes: 13 additions & 13 deletions explore/common/src/main/scala/explore/common/SimbadSearch.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package explore.common

import cats.data.NonEmptyChain
import cats.effect.*
import cats.effect.std.Random
import cats.syntax.all.*
import eu.timepit.refined.types.string.NonEmptyString
import explore.model.Constants
Expand All @@ -22,9 +23,9 @@ import java.util.concurrent.TimeoutException
import scala.concurrent.duration.*

object SimbadSearch {
import RetryHelpers.*
import lucuma.ui.utils.RetryHelpers.*

def search[F[_]](
def search[F[_]: Random](
term: NonEmptyString,
wildcard: Boolean = false
)(implicit F: Async[F], logger: Logger[F]): F[List[CatalogTargetResult]] =
Expand All @@ -39,7 +40,7 @@ object SimbadSearch {
searchSingle[F](uri, term, wildcard)
.raceAllToSuccess

private def searchSingle[F[_]](
private def searchSingle[F[_]: Random](
simbadUrl: Uri,
term: NonEmptyString,
wildcard: Boolean
Expand All @@ -56,16 +57,12 @@ object SimbadSearch {
else
baseURL

def isWorthRetrying(e: Throwable): F[Boolean] = e match {
case _: TimeoutException => F.pure(!wildcard)
case _ => F.pure(true)
}
def isWorthRetrying(e: Throwable): Boolean =
e match
case _: TimeoutException => !wildcard
case _ => true

retryingOnSomeErrors(
retryPolicy[F],
isWorthRetrying,
logError[F]("Simbad")
) {
retryingOnErrors {
FetchClientBuilder[F]
.withRequestTimeout(15.seconds)
.resource
Expand All @@ -83,6 +80,9 @@ object SimbadSearch {
case _ =>
Logger[F].error(s"Simbad search failed for term [$term]").as(List.empty)
}
}
}(
retryPolicy[F],
errorHandler = ResultHandler.retryOnSomeErrors(isWorthRetrying, log = logError[F]("Simbad"))
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,14 @@ object MainApp extends ServerEventHandler:
wsConnection <-
useResourceOnMount: // wsConnection to server (1)
Reconnect(
retryingOnAllErrors[WSConnectionHighLevel[IO]](
retryingOnErrors(
WebSocketClient[IO].connectHighLevel(WSRequest(EventWsUri))
)(
policy = WSRetryPolicy,
onError =
(_: Throwable, _) => syncStatus.async.set(SyncStatus.OutOfSync.some).toResource
)(WebSocketClient[IO].connectHighLevel(WSRequest(EventWsUri))),
errorHandler = ResultHandler.retryOnAllErrors(log =
(_, _) => syncStatus.async.set(SyncStatus.OutOfSync.some).toResource
)
),
_ => true.pure[IO]
).map(_.some)
clientConfigPot <- useStateView(Pot.pending[ClientConfig])
Expand Down
2 changes: 1 addition & 1 deletion project/Versions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ object Versions {
val cats = "2.13.0"
val catsEffect = "3.6.3"
val catsParse = "1.1.0"
val catsRetry = "3.1.3"
val catsRetry = "4.0.0"
val catsTime = "0.6.0"
val circe = "0.14.15"
val circeRefined = "0.15.1"
Expand Down
25 changes: 13 additions & 12 deletions ui/lib/src/main/scala/lucuma/ui/sso/SSOClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package lucuma.ui.sso

import cats.Applicative
import cats.effect.*
import cats.effect.std.Random
import cats.implicits.*
import eu.timepit.refined.*
import eu.timepit.refined.collection.NonEmpty
Expand All @@ -26,7 +27,7 @@ import java.util as ju

case class JwtOrcidProfile(exp: Long, `lucuma-user`: User) derives Decoder

case class SSOClient[F[_]: Async: Logger](config: SSOConfig):
case class SSOClient[F[_]: Async: Random: Logger](config: SSOConfig):
private val client = FetchClientBuilder[F]
.withRequestTimeout(config.readTimeout)
.withCredentials(RequestCredentials.include)
Expand All @@ -41,7 +42,7 @@ case class SSOClient[F[_]: Async: Logger](config: SSOConfig):
}

val guest: F[UserVault] =
retryingOnAllErrors(retryPolicy[F], logError[F]("Switching to guest")) {
retryingOnErrors {
client.use(
_.expect[String](Request[F](Method.POST, config.uri / "api" / "v1" / "auth-as-guest"))
.map(body =>
Expand All @@ -57,10 +58,10 @@ case class SSOClient[F[_]: Async: Logger](config: SSOConfig):
.getOrElse(throw new RuntimeException("Error decoding the token"))
)
)
}
}(retryPolicy[F], ResultHandler.retryOnAllErrors(log = logError[F]("Switching to guest")))

val whoami: F[Option[UserVault]] =
retryingOnAllErrors(retryPolicy[F], logError[F]("Calling whoami")) {
retryingOnErrors {
client
.flatMap(_.run(Request[F](Method.POST, config.uri / "api" / "v1" / "refresh-token")))
.use {
Expand All @@ -86,29 +87,29 @@ case class SSOClient[F[_]: Async: Logger](config: SSOConfig):
case _ =>
Applicative[F].pure(none[UserVault])
}
}
}(retryPolicy[F], ResultHandler.retryOnAllErrors(log = logError[F]("Calling whoami")))
.adaptError { case t =>
new Exception("Error connecting to authentication server.", t)
}

def switchRole(roleId: StandardRole.Id): F[Option[UserVault]] =
retryingOnAllErrors(retryPolicy[F], logError[F]("Switching role")) {
retryingOnErrors {
client
.flatMap(
_.run(
Request(Method.GET,
(config.uri / "auth" / "v1" / "set-role").withQueryParam("role", roleId.show)
Request(
Method.GET,
(config.uri / "auth" / "v1" / "set-role").withQueryParam("role", roleId.show)
)
)
)
.use_ *> whoami

}
}(retryPolicy[F], ResultHandler.retryOnAllErrors(log = logError[F]("Switching role")))

val logout: F[Unit] =
retryingOnAllErrors(retryPolicy[F], logError[F]("Calling logout")) {
retryingOnErrors {
client.flatMap(_.run(Request(Method.POST, config.uri / "api" / "v1" / "logout"))).use_
}
}(retryPolicy[F], ResultHandler.retryOnAllErrors(log = logError[F]("Calling logout")))

val switchToORCID: F[Unit] =
logout.attempt >> redirectToLogin
13 changes: 7 additions & 6 deletions ui/lib/src/main/scala/lucuma/ui/utils/RetryHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@ import java.util as ju
import scala.concurrent.duration.*

import ju.concurrent.TimeUnit
import cats.effect.std.Random

trait RetryHelpers:
def retryPolicy[F[_]: Applicative] =
def retryPolicy[F[_]: Applicative: Random] =
capDelay(
FiniteDuration.apply(5, TimeUnit.SECONDS),
fullJitter[F](FiniteDuration.apply(10, TimeUnit.MILLISECONDS))
).join(limitRetries[F](12))

def logError[F[_]: Logger](msg: String)(err: Throwable, details: RetryDetails): F[Unit] =
details match
case WillDelayAndRetry(_, retriesSoFar, _) =>
Logger[F].warn(err)(s"$msg failed - Will retry. Retries so far: [$retriesSoFar]")
case GivingUp(totalRetries, _) =>
Logger[F].error(err)(s"$msg failed - Giving up after [$totalRetries] retries.")
details.nextStepIfUnsuccessful match
case NextStep.DelayAndRetry(_) =>
Logger[F].warn(err)(s"$msg failed - Will retry. Retries so far: [${details.retriesSoFar}]")
case NextStep.GiveUp =>
Logger[F].error(err)(s"$msg failed - Giving up after [${details.retriesSoFar}] retries.")

object RetryHelpers extends RetryHelpers
Loading