1
1
package com .avast .clients .rabbitmq
2
2
3
- import cats .Applicative
4
3
import cats .effect .{Resource , Sync , Timer }
5
4
import cats .implicits .{catsSyntaxApplicativeError , catsSyntaxFlatMapOps , toFunctorOps }
6
5
import com .avast .bytes .Bytes
@@ -11,6 +10,7 @@ import com.avast.clients.rabbitmq.logging.ImplicitContextLogger
11
10
import com .avast .metrics .scalaeffectapi .{Meter , Monitor }
12
11
13
12
import java .time .Instant
13
+ import scala .concurrent .duration .FiniteDuration
14
14
import scala .reflect .ClassTag
15
15
import scala .util .Try
16
16
import scala .util .control .NonFatal
@@ -24,7 +24,7 @@ private trait PoisonedMessageHandlerAction[F[_], A] {
24
24
def handlePoisonedMessage (rawBody : Bytes )(delivery : Delivery [A ], maxAttempts : Int )(implicit dctx : DeliveryContext ): F [Unit ]
25
25
}
26
26
27
- private sealed abstract class PoisonedMessageHandlerBase [F [_]: Sync : Timer , A ](maxAttempts : Int ,
27
+ private abstract class PoisonedMessageHandlerBase [F [_]: Sync : Timer , A ](maxAttempts : Int ,
28
28
republishDelay : Option [ExponentialDelay ],
29
29
helper : PoisonedMessageHandlerHelper [F ])
30
30
extends PoisonedMessageHandler [F , A ]
@@ -69,11 +69,13 @@ private[rabbitmq] class NoOpPoisonedMessageHandler[F[_]: Sync, A](helper: Poison
69
69
extends PoisonedMessageHandler [F , A ] {
70
70
override def interceptResult (delivery : Delivery [A ], messageId : MessageId , rawBody : Bytes )(result : DeliveryResult )(
71
71
implicit dctx : DeliveryContext ): F [DeliveryResult ] = {
72
+ import helper ._
73
+
72
74
result match {
73
75
case DeliveryResult .DirectlyPoison =>
74
76
helper.logger.warn(" Delivery can't be poisoned, because NoOpPoisonedMessageHandler is installed! Rejecting instead..." ).as(Reject )
75
77
76
- case _ => Sync [ F ] .pure(result)
78
+ case _ => F .pure(result)
77
79
}
78
80
}
79
81
}
@@ -147,13 +149,15 @@ private[rabbitmq] object PoisonedMessageHandler {
147
149
helper : PoisonedMessageHandlerHelper [F ],
148
150
republishDelay : Option [ExponentialDelay ],
149
151
handler : PoisonedMessageHandlerAction [F , A ])(r : DeliveryResult )(implicit dctx : DeliveryContext ): F [DeliveryResult ] = {
152
+ import helper ._
153
+
150
154
r match {
151
155
case Republish (isPoisoned, newHeaders) if isPoisoned =>
152
156
adjustDeliveryResult(delivery, messageId, maxAttempts, newHeaders, helper, republishDelay, handler.handlePoisonedMessage(rawBody))
153
157
154
158
case DirectlyPoison => poisonRightAway(delivery, messageId, helper, handler.handlePoisonedMessage(rawBody))
155
159
156
- case r => Applicative [ F ] .pure(r) // keep other results as they are
160
+ case r => F .pure(r) // keep other results as they are
157
161
}
158
162
}
159
163
@@ -165,24 +169,23 @@ private[rabbitmq] object PoisonedMessageHandler {
165
169
helper : PoisonedMessageHandlerHelper [F ],
166
170
republishDelay : Option [ExponentialDelay ],
167
171
handlePoisonedMessage : (Delivery [A ], Int ) => F [Unit ])(implicit dctx : DeliveryContext ): F [DeliveryResult ] = {
168
- import cats .syntax .traverse ._
169
172
import helper ._
170
173
171
174
// get current attempt no. from passed headers with fallback to original (incoming) headers - the fallback will most likely happen
172
175
// but we're giving the programmer chance to programmatically _pretend_ lower attempt number
173
- val attempt = (delivery.properties.headers ++ newHeaders)
174
- .get(RepublishCountHeaderName )
175
- .flatMap(v => Try (v.toString.toInt).toOption)
176
- .getOrElse(0 ) + 1
176
+ val attempt = getCurrentAttempt(delivery, newHeaders)
177
177
178
178
logger.debug(s " Attempt $attempt/ $maxAttempts for $messageId" ) >> {
179
179
if (attempt < maxAttempts) {
180
- for {
181
- _ <- republishDelay.traverse { d =>
180
+ val republish =
181
+ Republish (countAsPoisoned = true , newHeaders = newHeaders + (RepublishCountHeaderName -> attempt.asInstanceOf [AnyRef ]))
182
+
183
+ republishDelay match {
184
+ case Some (d) =>
182
185
val delay = d.getExponentialDelay(attempt)
183
- logger.debug(s " Will republish the message in $delay" ) >> Timer [ F ].sleep (delay)
184
- }
185
- } yield Republish (countAsPoisoned = true , newHeaders = newHeaders + ( RepublishCountHeaderName -> attempt. asInstanceOf [ AnyRef ]))
186
+ logger.debug(s " Will republish the message in $delay" ) >> delayRepublish (delay)(republish )
187
+ case None => F .pure(republish)
188
+ }
186
189
} else {
187
190
val now = Instant .now()
188
191
@@ -201,40 +204,58 @@ private[rabbitmq] object PoisonedMessageHandler {
201
204
}
202
205
203
206
handlePoisonedMessage(finalDelivery, maxAttempts)
204
- .recoverWith {
205
- case NonFatal (e) => logger.warn(e)(" Poisoned message handler failed" )
206
- }
207
- .map(_ => Reject ) // always REJECT the message
207
+ .recoverWith { case NonFatal (e) => logger.warn(e)(" Poisoned message handler failed" ) }
208
+ .as(Reject ) // always REJECT the message
208
209
}
209
210
}
210
211
}
211
212
213
+ private def getCurrentAttempt [F [_]: Sync , A ](delivery : Delivery [A ], newHeaders : Map [String , AnyRef ]): Int = {
214
+ (delivery.properties.headers ++ newHeaders)
215
+ .get(RepublishCountHeaderName )
216
+ .flatMap(v => Try (v.toString.toInt).toOption)
217
+ .getOrElse(0 ) + 1
218
+ }
219
+
212
220
private def poisonRightAway [F [_]: Sync , A ](
213
221
delivery : Delivery [A ],
214
222
messageId : MessageId ,
215
223
helper : PoisonedMessageHandlerHelper [F ],
216
224
handlePoisonedMessage : (Delivery [A ], Int ) => F [Unit ])(implicit dctx : DeliveryContext ): F [DeliveryResult ] = {
217
- helper.logger.info(s " Directly poisoning delivery $messageId" ) >>
225
+ import helper ._
226
+
227
+ logger.info(s " Directly poisoning delivery $messageId" ) >>
218
228
handlePoisonedMessage(delivery, 0 ) >>
219
- helper. directlyPoisonedMeter.mark >>
220
- Sync [ F ] .pure(Reject : DeliveryResult )
229
+ directlyPoisonedMeter.mark >>
230
+ F .pure(Reject : DeliveryResult )
221
231
}
222
232
223
233
}
224
234
225
- private [rabbitmq] class PoisonedMessageHandlerHelper [F [_]: Sync ](val logger : ImplicitContextLogger [F ],
226
- val monitor : Monitor [F ],
227
- redactPayload : Boolean ) {
235
+ private [rabbitmq] class PoisonedMessageHandlerHelper [F [_]: Sync : Timer ](val logger : ImplicitContextLogger [F ],
236
+ val monitor : Monitor [F ],
237
+ redactPayload : Boolean ) {
238
+
239
+ val F : Sync [F ] = implicitly
228
240
229
241
val directlyPoisonedMeter : Meter [F ] = monitor.meter(" directlyPoisoned" )
230
242
243
+ private val delayingRepublishGauge = monitor.gauge.settableLong(" delayingRepublish" )
244
+
245
+ def delayRepublish (time : FiniteDuration )(r : Republish ): F [DeliveryResult ] = {
246
+ delayingRepublishGauge.inc >>
247
+ Timer [F ].sleep(time) >>
248
+ delayingRepublishGauge.dec >>
249
+ F .pure(r : DeliveryResult )
250
+ }
251
+
231
252
def redactIfConfigured (delivery : Delivery [_]): Delivery [Any ] = {
232
253
if (! redactPayload) delivery else delivery.withRedactedBody
233
254
}
234
255
}
235
256
236
257
private [rabbitmq] object PoisonedMessageHandlerHelper {
237
- def apply [F [_]: Sync , PMH : ClassTag ](monitor : Monitor [F ], redactPayload : Boolean ): PoisonedMessageHandlerHelper [F ] = {
258
+ def apply [F [_]: Sync : Timer , PMH : ClassTag ](monitor : Monitor [F ], redactPayload : Boolean ): PoisonedMessageHandlerHelper [F ] = {
238
259
val logger : ImplicitContextLogger [F ] = ImplicitContextLogger .createLogger[F , PMH ]
239
260
new PoisonedMessageHandlerHelper [F ](logger, monitor, redactPayload)
240
261
}
0 commit comments