1
1
package com .avast .clients .rabbitmq
2
2
3
- import cats .effect .concurrent .{Deferred , Ref }
4
- import cats .syntax .parallel ._
3
+ import cats .implicits .catsSyntaxParallelAp
5
4
import com .avast .bytes .Bytes
6
5
import com .avast .clients .rabbitmq .api .{MessageProperties , NotAcknowledgedPublish }
7
6
import com .avast .clients .rabbitmq .logging .ImplicitContextLogger
8
7
import com .avast .clients .rabbitmq .publisher .PublishConfirmsRabbitMQProducer
9
- import com .avast .clients .rabbitmq .publisher .PublishConfirmsRabbitMQProducer .SentMessages
10
8
import com .avast .metrics .scalaeffectapi .Monitor
11
9
import com .rabbitmq .client .impl .recovery .AutorecoveringChannel
12
10
import monix .eval .Task
13
11
import monix .execution .Scheduler .Implicits .global
12
+ import org .junit .runner .manipulation .InvalidOrderingException
14
13
import org .mockito .Matchers
15
14
import org .mockito .Matchers .any
16
15
import org .mockito .Mockito .{times , verify , when }
17
16
17
+ import scala .concurrent .Await
18
+ import scala .concurrent .duration .DurationInt
18
19
import scala .util .Random
19
20
20
21
class PublisherConfirmsRabbitMQProducerTest extends TestBase {
21
- test(" message is acked after one retry" ) {
22
+
23
+ test(" Message is acked after one retry" ) {
22
24
val exchangeName = Random .nextString(10 )
23
25
val routingKey = Random .nextString(10 )
24
26
val seqNumber = 1L
25
27
val seqNumber2 = 2L
26
28
27
29
val channel = mock[AutorecoveringChannel ]
28
- val ref = Ref .of[Task , Map [Long , Deferred [Task , Either [NotAcknowledgedPublish , Unit ]]]](Map .empty).await
29
- val updatedState1 = updateMessageState(ref, seqNumber)(Left (NotAcknowledgedPublish (" abcd" , messageId = seqNumber)))
30
- val updatedState2 = updateMessageState(ref, seqNumber2)(Right ())
30
+ when(channel.getNextPublishSeqNo).thenReturn(seqNumber, seqNumber2)
31
31
32
32
val producer = new PublishConfirmsRabbitMQProducer [Task , Bytes ](
33
33
name = " test" ,
@@ -39,15 +39,21 @@ class PublisherConfirmsRabbitMQProducerTest extends TestBase {
39
39
sizeLimitBytes = None ,
40
40
blocker = TestBase .testBlocker,
41
41
logger = ImplicitContextLogger .createLogger,
42
- sentMessages = ref,
43
42
sendAttempts = 2
44
43
)
45
- when(channel.getNextPublishSeqNo).thenReturn(seqNumber, seqNumber2)
46
44
47
- producer.send(routingKey, Bytes .copyFrom(Array .fill(499 )(32 .toByte))).parProduct(updatedState1.parProduct(updatedState2)).await
45
+ val body = Bytes .copyFrom(Array .fill(499 )(32 .toByte))
46
+
47
+ val publishTask = producer.send(routingKey, body).runToFuture
48
+
49
+ updateMessageState(producer, seqNumber)(Left (NotAcknowledgedPublish (" abcd" , messageId = seqNumber))).parProduct {
50
+ updateMessageState(producer, seqNumber2)(Right ())
51
+ }.await
52
+
53
+ Await .result(publishTask, 10 .seconds)
48
54
49
55
verify(channel, times(2 ))
50
- .basicPublish(Matchers .eq(exchangeName), Matchers .eq(routingKey), any(), Matchers .eq(Bytes .copyFrom( Array .fill( 499 )( 32 .toByte)) .toByteArray))
56
+ .basicPublish(Matchers .eq(exchangeName), Matchers .eq(routingKey), any(), Matchers .eq(body .toByteArray))
51
57
}
52
58
53
59
test(" Message not acked returned if number of attempts exhausted" ) {
@@ -56,8 +62,7 @@ class PublisherConfirmsRabbitMQProducerTest extends TestBase {
56
62
val seqNumber = 1L
57
63
58
64
val channel = mock[AutorecoveringChannel ]
59
- val ref = Ref .of[Task , Map [Long , Deferred [Task , Either [NotAcknowledgedPublish , Unit ]]]](Map .empty).await
60
- val updatedState = updateMessageState(ref, seqNumber)(Left (NotAcknowledgedPublish (" abcd" , messageId = seqNumber)))
65
+ when(channel.getNextPublishSeqNo).thenReturn(seqNumber)
61
66
62
67
val producer = new PublishConfirmsRabbitMQProducer [Task , Bytes ](
63
68
name = " test" ,
@@ -69,22 +74,76 @@ class PublisherConfirmsRabbitMQProducerTest extends TestBase {
69
74
sizeLimitBytes = None ,
70
75
blocker = TestBase .testBlocker,
71
76
logger = ImplicitContextLogger .createLogger,
72
- sentMessages = ref,
73
77
sendAttempts = 1
74
78
)
75
- when(channel.getNextPublishSeqNo).thenReturn(seqNumber)
79
+
80
+ val body = Bytes .copyFrom(Array .fill(499 )(32 .toByte))
81
+
82
+ val publishTask = producer.send(routingKey, body).runToFuture
76
83
77
84
assertThrows[NotAcknowledgedPublish ] {
78
- producer.send(routingKey, Bytes .copyFrom(Array .fill(499 )(32 .toByte))).parProduct(updatedState).await
85
+ updateMessageState(producer, seqNumber)(Left (NotAcknowledgedPublish (" abcd" , messageId = seqNumber))).await
86
+ Await .result(publishTask, 10 .seconds)
79
87
}
80
88
81
- verify(channel).basicPublish(Matchers .eq(exchangeName), Matchers .eq(routingKey), any(), Matchers .eq(Bytes .copyFrom(Array .fill(499 )(32 .toByte)).toByteArray))
89
+ verify(channel).basicPublish(Matchers .eq(exchangeName), Matchers .eq(routingKey), any(), Matchers .eq(body.toByteArray))
90
+ }
91
+
92
+ test(" Multiple messages are fully acked" ) {
93
+ val exchangeName = Random .nextString(10 )
94
+ val routingKey = Random .nextString(10 )
95
+
96
+ val channel = mock[AutorecoveringChannel ]
97
+
98
+ val seqNumbers = 1 to 500
99
+ val iterator = seqNumbers.iterator
100
+ when(channel.getNextPublishSeqNo).thenAnswer(_ => { iterator.next() })
101
+
102
+ val producer = new PublishConfirmsRabbitMQProducer [Task , Bytes ](
103
+ name = " test" ,
104
+ exchangeName = exchangeName,
105
+ channel = channel,
106
+ monitor = Monitor .noOp(),
107
+ defaultProperties = MessageProperties .empty,
108
+ reportUnroutable = false ,
109
+ sizeLimitBytes = None ,
110
+ blocker = TestBase .testBlocker,
111
+ logger = ImplicitContextLogger .createLogger,
112
+ sendAttempts = 2
113
+ )
114
+
115
+ val body = Bytes .copyFrom(Array .fill(499 )(32 .toByte))
116
+
117
+ val publishTasks = Task .parSequenceUnordered {
118
+ seqNumbers.map { _ =>
119
+ producer.send(routingKey, body)
120
+ }
121
+ }.runToFuture
122
+
123
+ Task
124
+ .parSequenceUnordered(seqNumbers.map { seqNumber =>
125
+ updateMessageState(producer, seqNumber)(Right ())
126
+ })
127
+ .await(15 .seconds)
128
+
129
+ Await .result(publishTasks, 15 .seconds)
130
+
131
+ verify(channel, times(seqNumbers.length))
132
+ .basicPublish(Matchers .eq(exchangeName), Matchers .eq(routingKey), any(), Matchers .eq(body.toByteArray))
82
133
}
83
134
84
- private def updateMessageState (ref : SentMessages [Task ], messageId : Long )(result : Either [NotAcknowledgedPublish , Unit ]): Task [Unit ] = {
85
- ref.get.flatMap(map => map.get(messageId) match {
86
- case Some (value) => value.complete(result)
87
- case None => updateMessageState(ref, messageId)(result)
88
- })
135
+ private def updateMessageState (producer : PublishConfirmsRabbitMQProducer [Task , Bytes ], messageId : Long , attempt : Int = 1 )(
136
+ result : Either [NotAcknowledgedPublish , Unit ]): Task [Unit ] = {
137
+ Task
138
+ .delay(producer.confirmationCallbacks.get(messageId))
139
+ .flatMap {
140
+ case Some (value) => value.complete(result)
141
+ case None =>
142
+ if (attempt < 90 ) {
143
+ Task .sleep(100 .millis) >> updateMessageState(producer, messageId, attempt + 1 )(result)
144
+ } else {
145
+ throw new InvalidOrderingException (s " The message ID $messageId is not present in the list of callbacks " )
146
+ }
147
+ }
89
148
}
90
149
}
0 commit comments