https://kotlinlang.org logo
#arrow
Title
# arrow
c

CLOVIS

09/23/2023, 9:43 PM
Am I wrong that
CircuitBreaker
only considers exceptions to be cause for breaking? In my case, exceptions are already caught into an
Either
upstream, so I'd rather break on
Either.Left
.
s

simon.vergauwen

09/25/2023, 6:09 AM
This is currently a bit tricky, but I would actually like to support it easily. There are a couple of options. 1. Expose different combinator for
protectRaise
, or
Either
. Naming will be tricky because we already have
protectEither
which returns
Either
instead of rethrowing the
ExecutionRejected
2. It's possible not possible to distinct between
CancellationException
from Kotlin(X) or Raise, but we could perhaps do that. If we do that, we could automatically increment the counters. By doing this we could perhaps also take that into account in
Schedule#retry
..🤔 What I mean with
2
is:
Copy code
either {
  schedule.retry { foo().bind() } // retry on Left
  cb.protect { foo().bind() } // increment failure counter 
}
c

CLOVIS

09/25/2023, 8:37 AM
Oh, so there would be a
Copy code
context(Raise<E>)
inline fun <E> CircuitBreaker.protect(block: context(Raise<E>) () -> Unit)
that has a try-catch on the specific Arrow CancellationException?
s

simon.vergauwen

09/25/2023, 8:41 AM
It could be completely transparent on top of the current
protect
, currently
NonFatal
are not being counted so
CancellationException
is not being counted
c

CLOVIS

09/25/2023, 8:50 AM
To go in more details of my exact use-case: as part of the Pedestal project I just mentioned, I have a bunch of Cache implementations that layer on top of each other to create complex strategies. Caches are reactive, that is, you can subscribe to a value, and when it gets refreshed, all subscribers are notified (classic
Flow
). In my projects using this architecture, all reads go through caches. Therefore, I think it would be a great place to use Arrow resilience: if for some reason the external service is dead, the cache should use CircuitBreaker (and maybe a retry? not sure) to failfast, serving the last cached value to the user until the service goes back up. Reading the Resilience documentation, this seems appropriate to me, but when trying to implement it, there is one challenge; the operation which may fail is not the one that should be retried. For example, here's how I would implement a naïve RetryCache:
Copy code
class RetryCache<I, F, V>(val upstream: Cache<I, F, V>) : Cache<I, F, V> by upstream {
    override val get(id: I): Flow<ProgressiveOutcome<F, V>> = upstream[id]
        // subscribe to all results…
        .onEach {
            if (it is ProgressiveOutcome.Failure)
                expire(id) // force a retry if the latest result failed, while still subscribing so we see the next result
        }
}
I'm not really sure how to adapt that to fit the
CircuitBreaker
's expectations.
s

simon.vergauwen

09/25/2023, 9:26 AM
Uhm, the operation itself occurs in
upstream
, right?
That's where you'd want the
CircuitBreaker
, no?
c

CLOVIS

09/25/2023, 11:15 AM
It's layered, and each layer asks the previous to do the operation. I'd like to have the circuit breaker as another layer so it can be composed with everything else
Probably a retry too, but I'm not used to Schedule enough to know if that's a good idea for now
s

simon.vergauwen

09/25/2023, 11:26 AM
It's layered, and each layer asks the previous to do the operation.
But you cannot wrap the operation, can you? 🤔
c

CLOVIS

09/25/2023, 11:27 AM
Well, the operation is
upstream[]
. But it's a flow.
If it wasn't a flow, I would just
Copy code
override fun get(id: I) = breaker.protect {
    upstream.get(id)
}
s

simon.vergauwen

09/25/2023, 11:28 AM
Right, exactly
Schedule
could be composed with
flow.retry(schedule)
so I guess
circuit-breaker
could do the same.. 🤔
I would need to look into the original
retryWhen
operator again to see what it does
2 Views