@@ -8,39 +8,47 @@ internal class Visitor(AmqpManagement management) : IVisitor
8
8
{
9
9
private AmqpManagement Management { get ; } = management ;
10
10
11
-
12
- public void VisitQueues ( List < QueueSpec > queueSpec )
11
+ public async Task VisitQueues ( List < QueueSpec > queueSpec )
13
12
{
14
13
foreach ( var spec in queueSpec )
15
14
{
16
15
Trace . WriteLine ( TraceLevel . Information , $ "Recovering queue { spec . Name } ") ;
17
- Management . Queue ( spec ) . Declare ( ) ;
16
+ await Management . Queue ( spec ) . Declare ( ) ;
18
17
}
19
18
}
20
19
}
21
20
22
-
23
21
/// <summary>
24
22
/// AmqpConnection is the concrete implementation of <see cref="IConnection"/>
25
23
/// It is a wrapper around the AMQP.Net Lite <see cref="Connection"/> class
26
24
/// </summary>
27
25
public class AmqpConnection : IConnection
28
26
{
27
+ private const string ConnectionNotRecoveredCode = "CONNECTION_NOT_RECOVERED" ;
28
+ private const string ConnectionNotRecoveredMessage = "Connection not recovered" ;
29
+
29
30
// The native AMQP.Net Lite connection
30
31
private Connection ? _nativeConnection ;
31
32
private readonly AmqpManagement _management = new ( ) ;
33
+
34
+
32
35
private readonly RecordingTopologyListener _recordingTopologyListener = new ( ) ;
36
+
33
37
private readonly ConnectionSettings _connectionSettings ;
34
38
35
39
/// <summary>
36
40
/// Creates a new instance of <see cref="AmqpConnection"/>
41
+ /// Through the Connection is possible to create:
42
+ /// - Management. See <see cref="AmqpManagement"/>
43
+ /// - Publishers and Consumers: TODO: Implement
37
44
/// </summary>
38
45
/// <param name="connectionSettings"></param>
39
46
/// <returns></returns>
40
47
public static async Task < AmqpConnection > CreateAsync ( ConnectionSettings connectionSettings )
41
48
{
42
49
var connection = new AmqpConnection ( connectionSettings ) ;
43
50
await connection . EnsureConnectionAsync ( ) ;
51
+
44
52
return connection ;
45
53
}
46
54
@@ -86,75 +94,116 @@ [new Symbol("connection_name")] = _connectionSettings.ConnectionName(),
86
94
_nativeConnection . AddClosedCallback ( MaybeRecoverConnection ( ) ) ;
87
95
}
88
96
89
- OnNewStatus ( Status . Open , null ) ;
97
+ OnNewStatus ( State . Open , null ) ;
90
98
}
91
99
catch ( AmqpException e )
92
100
{
93
- throw new ConnectionException ( "AmqpException: Connection failed" , e ) ;
101
+ throw new ConnectionException ( $ "AmqpException: Connection failed. Info: { ToString ( ) } ", e ) ;
94
102
}
95
103
catch ( OperationCanceledException e )
96
104
{
97
105
// wrong virtual host
98
- throw new ConnectionException ( "OperationCanceledException: Connection failed" , e ) ;
106
+ throw new ConnectionException ( $ "OperationCanceledException: Connection failed. Info: { ToString ( ) } ", e ) ;
99
107
}
100
108
101
109
catch ( NotSupportedException e )
102
110
{
103
111
// wrong schema
104
- throw new ConnectionException ( "NotSupportedException: Connection failed" , e ) ;
112
+ throw new ConnectionException ( $ "NotSupportedException: Connection failed. Info: { ToString ( ) } ", e ) ;
105
113
}
106
114
}
107
115
108
-
109
- private void OnNewStatus ( Status newStatus , Error ? error )
116
+
117
+ private void OnNewStatus ( State newState , Error ? error )
110
118
{
111
- if ( Status == newStatus ) return ;
112
- var oldStatus = Status ;
113
- Status = newStatus ;
114
- ChangeStatus ? . Invoke ( this , oldStatus , newStatus , error ) ;
119
+ if ( State == newState ) return ;
120
+ var oldStatus = State ;
121
+ State = newState ;
122
+ ChangeState ? . Invoke ( this , oldStatus , newState , error ) ;
115
123
}
116
124
117
125
private ClosedCallback MaybeRecoverConnection ( )
118
126
{
119
- return ( sender , error ) =>
127
+ return async ( sender , error ) =>
120
128
{
121
129
if ( error != null )
122
130
{
123
- // TODO: Implement Dump Interface
124
- Trace . WriteLine ( TraceLevel . Warning , $ "connection is closed unexpected" +
125
- $ "{ sender } { error } { Status } " +
126
- $ "{ _nativeConnection ! . IsClosed } ") ;
131
+ Trace . WriteLine ( TraceLevel . Warning , $ "connection is closed unexpectedly. " +
132
+ $ "Info: { ToString ( ) } ") ;
127
133
128
134
if ( ! _connectionSettings . RecoveryConfiguration . IsActivate ( ) )
129
135
{
130
- OnNewStatus ( Status . Closed , Utils . ConvertError ( error ) ) ;
136
+ OnNewStatus ( State . Closed , Utils . ConvertError ( error ) ) ;
131
137
return ;
132
138
}
133
139
140
+ // TODO: Block the publishers and consumers
141
+ OnNewStatus ( State . Reconnecting , Utils . ConvertError ( error ) ) ;
134
142
135
- OnNewStatus ( Status . Reconneting , Utils . ConvertError ( error ) ) ;
136
-
137
- Thread . Sleep ( 1000 ) ;
138
- // TODO: Replace with Backoff pattern
139
- var t = Task . Run ( async ( ) =>
143
+ await Task . Run ( async ( ) =>
140
144
{
141
- Trace . WriteLine ( TraceLevel . Information , "Recovering connection" ) ;
142
- await EnsureConnectionAsync ( ) ;
143
- Trace . WriteLine ( TraceLevel . Information , "Recovering topology" ) ;
145
+ var connected = false ;
146
+ // as first step we try to recover the connection
147
+ // so the connected flag is false
148
+ while ( ! connected &&
149
+ // we have to check if the recovery is active.
150
+ // The user may want to disable the recovery mechanism
151
+ // the user can use the lifecycle callback to handle the error
152
+ _connectionSettings . RecoveryConfiguration . IsActivate ( ) &&
153
+ // we have to check if the backoff policy is active
154
+ // the user may want to disable the backoff policy or
155
+ // the backoff policy is not active due of some condition
156
+ // for example: Reaching the maximum number of retries and avoid the forever loop
157
+ _connectionSettings . RecoveryConfiguration . GetBackOffDelayPolicy ( ) . IsActive )
158
+ {
159
+ try
160
+ {
161
+ var next = _connectionSettings . RecoveryConfiguration . GetBackOffDelayPolicy ( ) . Delay ( ) ;
162
+ Trace . WriteLine ( TraceLevel . Information ,
163
+ $ "Trying Recovering connection in { next } milliseconds. Info: { ToString ( ) } )") ;
164
+ await Task . Delay (
165
+ TimeSpan . FromMilliseconds ( next ) ) ;
166
+
167
+ await EnsureConnectionAsync ( ) ;
168
+ connected = true ;
169
+ }
170
+ catch ( Exception e )
171
+ {
172
+ Trace . WriteLine ( TraceLevel . Warning ,
173
+ $ "Error trying to recover connection { e } . Info: { this } ") ;
174
+ }
175
+ }
176
+
177
+ _connectionSettings . RecoveryConfiguration . GetBackOffDelayPolicy ( ) . Reset ( ) ;
178
+ var connectionDescription = connected ? "recovered" : "not recovered" ;
179
+ Trace . WriteLine ( TraceLevel . Information ,
180
+ $ "Connection { connectionDescription } . Info: { ToString ( ) } ") ;
181
+
182
+ if ( ! connected )
183
+ {
184
+ Trace . WriteLine ( TraceLevel . Verbose , $ "connection is closed. Info: { ToString ( ) } ") ;
185
+ OnNewStatus ( State . Closed , new Error ( )
186
+ {
187
+ Description =
188
+ $ "{ ConnectionNotRecoveredMessage } , recover status: { _connectionSettings . RecoveryConfiguration } ",
189
+ ErrorCode = ConnectionNotRecoveredCode
190
+ } ) ;
191
+ return ;
192
+ }
193
+
194
+
144
195
if ( _connectionSettings . RecoveryConfiguration . IsTopologyActive ( ) )
145
196
{
146
- _recordingTopologyListener . Accept ( new Visitor ( _management ) ) ;
197
+ Trace . WriteLine ( TraceLevel . Information , $ "Recovering topology. Info: { ToString ( ) } ") ;
198
+ await _recordingTopologyListener . Accept ( new Visitor ( _management ) ) ;
147
199
}
148
200
} ) ;
149
- t . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
150
201
return ;
151
202
}
152
203
153
204
154
- Trace . WriteLine ( TraceLevel . Verbose , $ "connection is closed" +
155
- $ "{ sender } { error } { Status } " +
156
- $ "{ _nativeConnection ! . IsClosed } ") ;
157
- OnNewStatus ( Status . Closed , Utils . ConvertError ( error ) ) ;
205
+ Trace . WriteLine ( TraceLevel . Verbose , $ "connection is closed. Info: { ToString ( ) } ") ;
206
+ OnNewStatus ( State . Closed , Utils . ConvertError ( error ) ) ;
158
207
} ;
159
208
}
160
209
@@ -166,12 +215,20 @@ private ClosedCallback MaybeRecoverConnection()
166
215
167
216
public async Task CloseAsync ( )
168
217
{
169
- OnNewStatus ( Status . Closed , null ) ;
218
+ _recordingTopologyListener . Clear ( ) ;
219
+ if ( State == State . Closed ) return ;
220
+ OnNewStatus ( State . Closing , null ) ;
170
221
if ( _nativeConnection is { IsClosed : false } ) await _nativeConnection . CloseAsync ( ) ;
171
222
await _management . CloseAsync ( ) ;
172
223
}
173
224
174
- public event IClosable . ChangeStatusCallBack ? ChangeStatus ;
225
+ public event IClosable . LifeCycleCallBack ? ChangeState ;
226
+
227
+ public State State { get ; private set ; } = State . Closed ;
175
228
176
- public Status Status { get ; private set ; } = Status . Closed ;
229
+ public override string ToString ( )
230
+ {
231
+ var info = $ "AmqpConnection{{ConnectionSettings='{ _connectionSettings } ', Status='{ State . ToString ( ) } '}}";
232
+ return info ;
233
+ }
177
234
}
0 commit comments