1
1
package com .avast .clients .rabbitmq
2
2
3
- import cats .implicits .catsSyntaxParallelAp
4
3
import com .avast .bytes .Bytes
5
4
import com .avast .clients .rabbitmq .api .{MessageProperties , NotAcknowledgedPublish }
6
5
import com .avast .clients .rabbitmq .logging .ImplicitContextLogger
@@ -9,7 +8,7 @@ import com.avast.metrics.scalaeffectapi.Monitor
9
8
import com .rabbitmq .client .impl .recovery .AutorecoveringChannel
10
9
import monix .eval .Task
11
10
import monix .execution .Scheduler .Implicits .global
12
- import org . junit . runner . manipulation . InvalidOrderingException
11
+ import monix . execution . atomic . AtomicInt
13
12
import org .mockito .Matchers
14
13
import org .mockito .Matchers .any
15
14
import org .mockito .Mockito .{times , verify , when }
@@ -23,11 +22,13 @@ class PublisherConfirmsRabbitMQProducerTest extends TestBase {
23
22
test(" Message is acked after one retry" ) {
24
23
val exchangeName = Random .nextString(10 )
25
24
val routingKey = Random .nextString(10 )
26
- val seqNumber = 1L
27
- val seqNumber2 = 2L
28
25
26
+ val nextSeqNumber = AtomicInt (0 )
29
27
val channel = mock[AutorecoveringChannel ]
30
- when(channel.getNextPublishSeqNo).thenReturn(seqNumber, seqNumber2)
28
+ when(channel.getNextPublishSeqNo).thenAnswer(_ => nextSeqNumber.get())
29
+ when(channel.basicPublish(any(), any(), any(), any())).thenAnswer(_ => {
30
+ nextSeqNumber.increment()
31
+ })
31
32
32
33
val producer = new PublishConfirmsRabbitMQProducer [Task , Bytes ](
33
34
name = " test" ,
@@ -44,13 +45,15 @@ class PublisherConfirmsRabbitMQProducerTest extends TestBase {
44
45
45
46
val body = Bytes .copyFrom(Array .fill(499 )(32 .toByte))
46
47
47
- val publishTask = producer.send(routingKey, body).runToFuture
48
+ val publishFuture = producer.send(routingKey, body).runToFuture
49
+
50
+ while (nextSeqNumber.get() < 1 ) { Thread .sleep(5 ) }
51
+ producer.DefaultConfirmListener .handleNack(0 , multiple = false )
48
52
49
- updateMessageState(producer, seqNumber)(Left (NotAcknowledgedPublish (" abcd" , messageId = seqNumber))).parProduct {
50
- updateMessageState(producer, seqNumber2)(Right ())
51
- }.await
53
+ while (nextSeqNumber.get() < 2 ) { Thread .sleep(5 ) }
54
+ producer.DefaultConfirmListener .handleAck(1 , multiple = false )
52
55
53
- Await .result(publishTask , 10 .seconds)
56
+ Await .result(publishFuture , 10 .seconds)
54
57
55
58
verify(channel, times(2 ))
56
59
.basicPublish(Matchers .eq(exchangeName), Matchers .eq(routingKey), any(), Matchers .eq(body.toByteArray))
@@ -59,10 +62,13 @@ class PublisherConfirmsRabbitMQProducerTest extends TestBase {
59
62
test(" Message not acked returned if number of attempts exhausted" ) {
60
63
val exchangeName = Random .nextString(10 )
61
64
val routingKey = Random .nextString(10 )
62
- val seqNumber = 1L
63
65
66
+ val nextSeqNumber = AtomicInt (0 )
64
67
val channel = mock[AutorecoveringChannel ]
65
- when(channel.getNextPublishSeqNo).thenReturn(seqNumber)
68
+ when(channel.getNextPublishSeqNo).thenAnswer(_ => nextSeqNumber.get())
69
+ when(channel.basicPublish(any(), any(), any(), any())).thenAnswer(_ => {
70
+ nextSeqNumber.increment()
71
+ })
66
72
67
73
val producer = new PublishConfirmsRabbitMQProducer [Task , Bytes ](
68
74
name = " test" ,
@@ -77,27 +83,35 @@ class PublisherConfirmsRabbitMQProducerTest extends TestBase {
77
83
sendAttempts = 1
78
84
)
79
85
80
- val body = Bytes .copyFrom(Array .fill(499 )(32 .toByte))
86
+ val body = Bytes .copyFrom(Array .fill(499 )(64 .toByte))
81
87
82
88
val publishTask = producer.send(routingKey, body).runToFuture
83
89
90
+ while (nextSeqNumber.get() < 1 ) {
91
+ Thread .sleep(5 )
92
+ }
93
+
94
+ producer.DefaultConfirmListener .handleNack(0 , multiple = false )
95
+
84
96
assertThrows[NotAcknowledgedPublish ] {
85
- updateMessageState(producer, seqNumber)(Left (NotAcknowledgedPublish (" abcd" , messageId = seqNumber))).await
86
- Await .result(publishTask, 10 .seconds)
97
+ Await .result(publishTask, 1 .seconds)
87
98
}
88
99
89
100
verify(channel).basicPublish(Matchers .eq(exchangeName), Matchers .eq(routingKey), any(), Matchers .eq(body.toByteArray))
90
101
}
91
102
92
- test(" Multiple messages are fully acked" ) {
103
+ test(" Multiple messages are fully acked one by one " ) {
93
104
val exchangeName = Random .nextString(10 )
94
105
val routingKey = Random .nextString(10 )
95
106
96
- val channel = mock[AutorecoveringChannel ]
107
+ val seqNumbers = (0 to 499 ).toList
108
+ val nextSeqNumber = AtomicInt (0 )
97
109
98
- val seqNumbers = 1 to 500
99
- val iterator = seqNumbers.iterator
100
- when(channel.getNextPublishSeqNo).thenAnswer(_ => { iterator.next() })
110
+ val channel = mock[AutorecoveringChannel ]
111
+ when(channel.getNextPublishSeqNo).thenAnswer(_ => nextSeqNumber.get())
112
+ when(channel.basicPublish(any(), any(), any(), any())).thenAnswer(_ => {
113
+ nextSeqNumber.increment()
114
+ })
101
115
102
116
val producer = new PublishConfirmsRabbitMQProducer [Task , Bytes ](
103
117
name = " test" ,
@@ -112,38 +126,64 @@ class PublisherConfirmsRabbitMQProducerTest extends TestBase {
112
126
sendAttempts = 2
113
127
)
114
128
115
- val body = Bytes .copyFrom(Array .fill(499 )(32 .toByte))
129
+ val body = Bytes .copyFrom(Array .fill(499 )(Random .nextInt( 255 ) .toByte))
116
130
117
- val publishTasks = Task .parSequenceUnordered {
118
- seqNumbers.map { _ =>
119
- producer.send(routingKey, body)
120
- }
131
+ val publishFuture = Task .parSequence {
132
+ seqNumbers.map(_ => producer.send(routingKey, body))
121
133
}.runToFuture
122
134
123
- Task
124
- .parSequenceUnordered(seqNumbers.map { seqNumber =>
125
- updateMessageState(producer, seqNumber)(Right ())
126
- })
127
- .await(15 .seconds)
135
+ seqNumbers.foreach { seqNumber =>
136
+ while (nextSeqNumber.get() <= seqNumber) { Thread .sleep(5 ) }
137
+ producer.DefaultConfirmListener .handleAck(seqNumber, multiple = false )
138
+ }
128
139
129
- Await .result(publishTasks , 15 .seconds)
140
+ Await .result(publishFuture , 15 .seconds)
130
141
142
+ assertResult(seqNumbers.length)(nextSeqNumber.get())
131
143
verify(channel, times(seqNumbers.length))
132
144
.basicPublish(Matchers .eq(exchangeName), Matchers .eq(routingKey), any(), Matchers .eq(body.toByteArray))
133
145
}
134
146
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
- }
147
+ test(" Multiple messages are fully acked at once" ) {
148
+ val exchangeName = Random .nextString(10 )
149
+ val routingKey = Random .nextString(10 )
150
+
151
+ val messageCount = 500
152
+ val seqNumbers = (0 until messageCount).toList
153
+ val nextSeqNumber = AtomicInt (0 )
154
+
155
+ val channel = mock[AutorecoveringChannel ]
156
+ when(channel.getNextPublishSeqNo).thenAnswer(_ => nextSeqNumber.get())
157
+ when(channel.basicPublish(any(), any(), any(), any())).thenAnswer(_ => {
158
+ nextSeqNumber.increment()
159
+ })
160
+
161
+ val producer = new PublishConfirmsRabbitMQProducer [Task , Bytes ](
162
+ name = " test" ,
163
+ exchangeName = exchangeName,
164
+ channel = channel,
165
+ monitor = Monitor .noOp(),
166
+ defaultProperties = MessageProperties .empty,
167
+ reportUnroutable = false ,
168
+ sizeLimitBytes = None ,
169
+ blocker = TestBase .testBlocker,
170
+ logger = ImplicitContextLogger .createLogger,
171
+ sendAttempts = 2
172
+ )
173
+
174
+ val body = Bytes .copyFrom(Array .fill(499 )(Random .nextInt(255 ).toByte))
175
+
176
+ val publishFuture = Task .parSequence {
177
+ seqNumbers.map(_ => producer.send(routingKey, body))
178
+ }.runToFuture
179
+
180
+ while (nextSeqNumber.get() < messageCount) { Thread .sleep(5 ) }
181
+ producer.DefaultConfirmListener .handleAck(messageCount, multiple = true )
182
+
183
+ Await .result(publishFuture, 15 .seconds)
184
+
185
+ assertResult(seqNumbers.length)(nextSeqNumber.get())
186
+ verify(channel, times(seqNumbers.length))
187
+ .basicPublish(Matchers .eq(exchangeName), Matchers .eq(routingKey), any(), Matchers .eq(body.toByteArray))
148
188
}
149
189
}
0 commit comments