1414
1515namespace ReliableClient ;
1616
17- public class RClient
17+ public class BestPracticesClient
1818{
1919 public record Config
2020 {
21- public string Host { get ; set ; } = "localhost" ;
21+ public string ? Host { get ; set ; } = "localhost" ;
2222 public int Port { get ; set ; } = 5552 ;
23- public string Username { get ; set ; } = "guest" ;
24- public string Password { get ; set ; } = "guest" ;
23+ public string ? Username { get ; set ; } = "guest" ;
24+ public string ? Password { get ; set ; } = "guest" ;
25+
26+ public string ? StreamName { get ; set ; } = "DotNetClientTest" ;
2527 public bool LoadBalancer { get ; set ; } = false ;
2628 public bool SuperStream { get ; set ; } = false ;
2729 public int Streams { get ; set ; } = 1 ;
@@ -30,6 +32,10 @@ public record Config
3032 public int MessagesPerProducer { get ; set ; } = 5_000_000 ;
3133 public int Consumers { get ; set ; } = 9 ;
3234 public byte ConsumersPerConnection { get ; set ; } = 8 ;
35+
36+ public int DelayDuringSendMs { get ; set ; } = 0 ;
37+
38+
3339 }
3440
3541 public static async Task Start ( Config config )
@@ -61,12 +67,18 @@ public static async Task Start(Config config)
6167 switch ( Uri . CheckHostName ( config . Host ) )
6268 {
6369 case UriHostNameType . IPv4 :
64- ep = new IPEndPoint ( IPAddress . Parse ( config . Host ) , config . Port ) ;
70+ if ( config . Host != null ) ep = new IPEndPoint ( IPAddress . Parse ( config . Host ) , config . Port ) ;
6571 break ;
6672 case UriHostNameType . Dns :
67- var addresses = await Dns . GetHostAddressesAsync ( config . Host ) . ConfigureAwait ( false ) ;
68- ep = new IPEndPoint ( addresses [ 0 ] , config . Port ) ;
73+ if ( config . Host != null )
74+ {
75+ var addresses = await Dns . GetHostAddressesAsync ( config . Host ) . ConfigureAwait ( false ) ;
76+ ep = new IPEndPoint ( addresses [ 0 ] , config . Port ) ;
77+ }
78+
6979 break ;
80+ default :
81+ throw new ArgumentOutOfRangeException ( ) ;
7082 }
7183 }
7284
@@ -105,13 +117,13 @@ public static async Task Start(Config config)
105117 var streamsList = new List < string > ( ) ;
106118 if ( config . SuperStream )
107119 {
108- streamsList . Add ( "invoices" ) ;
120+ if ( config . StreamName != null ) streamsList . Add ( config . StreamName ) ;
109121 }
110122 else
111123 {
112124 for ( var i = 0 ; i < config . Streams ; i ++ )
113125 {
114- streamsList . Add ( $ "invoices -{ i } ") ;
126+ streamsList . Add ( $ "{ config . StreamName } -{ i } ") ;
115127 }
116128 }
117129
@@ -141,6 +153,18 @@ public static async Task Start(Config config)
141153 List < Consumer > consumersList = new ( ) ;
142154 List < Producer > producersList = new ( ) ;
143155 var obj = new object ( ) ;
156+ if ( config . SuperStream )
157+ {
158+ if ( await system . SuperStreamExists ( streamsList [ 0 ] ) . ConfigureAwait ( false ) )
159+ {
160+ await system . DeleteSuperStream ( streamsList [ 0 ] ) . ConfigureAwait ( false ) ;
161+ }
162+
163+
164+ await system . CreateSuperStream ( new PartitionsSuperStreamSpec ( streamsList [ 0 ] , config . Streams ) ) . ConfigureAwait ( false ) ;
165+ }
166+
167+
144168 foreach ( var stream in streamsList )
145169 {
146170 if ( ! config . SuperStream )
@@ -153,7 +177,7 @@ public static async Task Start(Config config)
153177 await system . CreateStream ( new StreamSpec ( stream ) { MaxLengthBytes = 30_000_000_000 , } )
154178 . ConfigureAwait ( false ) ;
155179 await Task . Delay ( TimeSpan . FromSeconds ( 3 ) ) . ConfigureAwait ( false ) ;
156- }
180+ }
157181
158182 for ( var z = 0 ; z < config . Consumers ; z ++ )
159183 {
@@ -162,15 +186,24 @@ await system.CreateStream(new StreamSpec(stream) {MaxLengthBytes = 30_000_000_00
162186 OffsetSpec = new OffsetTypeLast ( ) ,
163187 IsSuperStream = config . SuperStream ,
164188 IsSingleActiveConsumer = config . SuperStream ,
165- Reference = "myApp" ,
166- Identifier = $ "my_c_{ z } ",
189+ Reference = "myApp" , // needed for the Single Active Consumer or fot the store offset
190+ // can help to identify the consumer on the logs and RabbitMQ Management
191+ Identifier = $ "my_consumer_{ z } ",
167192 InitialCredits = 10 ,
168- MessageHandler = ( source , ctx , _ , _ ) =>
193+ MessageHandler = async ( source , consumer , ctx , _ ) =>
169194 {
195+ if ( totalConsumed % 10_000 == 0 )
196+ {
197+ // don't store the offset every time, it could be a performance issue
198+ // store the offset every 1_000/5_000/10_000 messages
199+ await consumer . StoreOffset ( ctx . Offset ) . ConfigureAwait ( false ) ;
200+ }
170201 Interlocked . Increment ( ref totalConsumed ) ;
171- return Task . CompletedTask ;
172202 } ,
173203 } ;
204+
205+ // This is the callback that will be called when the consumer status changes
206+ // DON'T PUT ANY BLOCKING CODE HERE
174207 conf . StatusChanged += ( status ) =>
175208 {
176209 var streamInfo = status . Partition is not null
@@ -190,27 +223,33 @@ async Task MaybeSend(Producer producer, Message message, ManualResetEvent publis
190223 await producer . Send ( message ) . ConfigureAwait ( false ) ;
191224 }
192225
226+ // this example is meant to show how to use the producer and consumer
227+ // Create too many tasks for the producers and consumers is not a good idea
193228 for ( var z = 0 ; z < config . Producers ; z ++ )
194229 {
195230 var z1 = z ;
196231 _ = Task . Run ( async ( ) =>
197232 {
233+ // the list of unconfirmed messages in case of error or disconnection
234+ // This example is only for the example, in a real scenario you should handle the unconfirmed messages
235+ // since the list could grow event the publishEvent should avoid it.
198236 var unconfirmedMessages = new ConcurrentBag < Message > ( ) ;
237+ // the event to wait for the producer to be ready to send
238+ // in case of disconnection the event will be reset
199239 var publishEvent = new ManualResetEvent ( false ) ;
200-
201240 var producerConfig = new ProducerConfig ( system , stream )
202241 {
203- Identifier = $ "my_super_ { z1 } ",
242+ Identifier = $ "my_producer_ { z1 } ",
204243 SuperStreamConfig = new SuperStreamConfig ( )
205244 {
206245 Enabled = config . SuperStream , Routing = msg => msg . Properties . MessageId . ToString ( ) ,
207246 } ,
208247 ConfirmationHandler = confirmation =>
209248 {
249+ // Add the unconfirmed messages to the list in case of error
210250 if ( confirmation . Status != ConfirmationStatus . Confirmed )
211251 {
212252 confirmation . Messages . ForEach ( m => { unconfirmedMessages . Add ( m ) ; } ) ;
213-
214253 Interlocked . Add ( ref totalError , confirmation . Messages . Count ) ;
215254 return Task . CompletedTask ;
216255 }
@@ -219,15 +258,21 @@ async Task MaybeSend(Producer producer, Message message, ManualResetEvent publis
219258 return Task . CompletedTask ;
220259 } ,
221260 } ;
261+
262+ // Like the consumer don't put any blocking code here
222263 producerConfig . StatusChanged += ( status ) =>
223264 {
224265 var streamInfo = status . Partition is not null
225266 ? $ " Partition { status . Partition } of super stream: { status . Stream } "
226267 : $ "Stream: { status . Stream } ";
227268
269+ // just log the status change
228270 lp . LogInformation ( "Consumer: {Id} - status changed from: {From} to: {To} reason: {Reason} {Info}" ,
229271 status . Identifier , status . From , status . To , status . Reason , streamInfo ) ;
230272
273+ // in case of disconnection the event will be reset
274+ // in case of reconnection the event will be set so the producer can send messages
275+ // It is important to use the ManualReset to avoid to send messages before the producer is ready
231276 if ( status . To == ReliableEntityStatus . Open )
232277 {
233278 publishEvent . Set ( ) ;
@@ -247,6 +292,7 @@ async Task MaybeSend(Producer producer, Message message, ManualResetEvent publis
247292 {
248293 if ( ! unconfirmedMessages . IsEmpty )
249294 {
295+ // checks if there are unconfirmed messages and send them
250296 var msgs = unconfirmedMessages . ToArray ( ) ;
251297 unconfirmedMessages . Clear ( ) ;
252298 foreach ( var msg in msgs )
@@ -261,7 +307,8 @@ async Task MaybeSend(Producer producer, Message message, ManualResetEvent publis
261307 Properties = new Properties ( ) { MessageId = $ "hello{ i } "}
262308 } ;
263309 await MaybeSend ( producer , message , publishEvent ) . ConfigureAwait ( false ) ;
264- // await Task.Delay(1).ConfigureAwait(false);
310+ // You don't need this it is only for the example
311+ await Task . Delay ( config . DelayDuringSendMs ) . ConfigureAwait ( false ) ;
265312 Interlocked . Increment ( ref totalSent ) ;
266313 }
267314 } ) ;
0 commit comments