1
+ // Copyright (c) 2008-2022, Hazelcast, Inc. All Rights Reserved.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ using System ;
16
+ using System . Collections . Generic ;
17
+ using System . Linq ;
18
+ using System . Threading ;
19
+ using System . Threading . Tasks ;
20
+ using Hazelcast . Core ;
21
+ using Hazelcast . Exceptions ;
22
+ using Hazelcast . Messaging ;
23
+ using Hazelcast . Networking ;
24
+ using Hazelcast . Partitioning ;
25
+ using Hazelcast . Protocol . Codecs ;
26
+ using Hazelcast . Serialization ;
27
+ using Hazelcast . Testing ;
28
+ using Hazelcast . Testing . Logging ;
29
+ using NUnit . Framework ;
30
+
31
+ namespace Hazelcast . Tests . Serialization . Compact ;
32
+
33
+ [ TestFixture ]
34
+ public class CompactQaTests : ClusterRemoteTestBase
35
+ {
36
+ private IDisposable UseHConsole ( ) => HConsole . Capture ( o => o
37
+ //.WithFilename("console.out")
38
+ . Configure ( ) . SetMaxLevel ( ) . EnableTimeStamp ( origin : DateTime . Now )
39
+ . Configure ( this ) . SetPrefix ( "TEST" )
40
+ . Configure < SocketConnectionBase > ( ) . SetIndent ( 8 ) . SetPrefix ( "SOCKET" ) . SetLevel ( 0 )
41
+ . Configure < ClientMessageConnection > ( ) . SetMinLevel ( )
42
+ . Configure < AsyncContext > ( ) . SetMinLevel ( )
43
+ . Configure < Partitioner > ( ) . SetLevel ( 1 ) ) ;
44
+
45
+ [ Test ]
46
+ [ Explicit ( "See comment in test." ) ]
47
+ public async Task MemberAddressMatch ( )
48
+ {
49
+ using var _ = UseHConsole ( ) ;
50
+
51
+ var member = await RcClient . StartMemberAsync ( RcCluster ) . CfAwait ( ) ;
52
+ await using var cleanup = new DisposeAsyncAction ( async ( ) => await RcClient . StopMemberAsync ( RcCluster , member ) ) ;
53
+
54
+ var options = CreateHazelcastOptions ( ) ;
55
+ options . Networking . ConnectionRetry . ClusterConnectionTimeoutMilliseconds = 10_000 ;
56
+ options . Networking . Addresses . Clear ( ) ;
57
+ options . Networking . Addresses . Add ( "127.0.0.2:5701" ) ;
58
+
59
+ // this relies on the connect queue running all the time, but we have now disabled
60
+ // the queue when disconnected - so this test cannot work anymore, we will need to
61
+ // find a different way to do address re-routing in the future, if other clients
62
+ // do it too.
63
+
64
+ await using var client = await HazelcastClientFactory . StartNewClientAsync ( options ) ;
65
+ }
66
+
67
+ [ TestCase ( true ) ]
68
+ [ TestCase ( false ) ]
69
+ [ Explicit ]
70
+ public async Task ExceptionPreventsClientFromReconnecting ( bool recover )
71
+ {
72
+ using var _ = UseHConsole ( ) ;
73
+
74
+ // in case a test times out, NUnit just reports that it failed, without further
75
+ // details - in fact, it does not even say that the test timed out - so by wrapping
76
+ // the actual test method this way, we make sure that the test "fails" instead of
77
+ // timing out, and we get proper output (eg HConsole output)
78
+ await ExceptionPreventsClientFromReconnectingTask ( recover ) . CfAwait ( TimeSpan . FromSeconds ( 60 ) ) ;
79
+ }
80
+
81
+ private async Task ExceptionPreventsClientFromReconnectingTask ( bool recover )
82
+ {
83
+ var throwException = false ;
84
+
85
+ // start a member
86
+ var member = await RcClient . StartMemberAsync ( RcCluster ) . CfAwait ( ) ;
87
+ await using var cleanup = new DisposeAsyncAction ( async ( ) => await RcClient . StopMemberAsync ( RcCluster , member ) ) ;
88
+
89
+ // use a clean client + hook into cluster messaging to capture messages (before client starts)
90
+ var options = CreateHazelcastOptions ( ) ;
91
+ options . Messaging . RetryTimeoutSeconds = 20 ;
92
+ await using var client = HazelcastClientFactory . CreateClient ( options ) ;
93
+ // use an internal-level handler, exceptions in user-level handlers are caught
94
+ client . Cluster . Connections . ConnectionOpened += ( _ , _ , _ , _ ) =>
95
+ {
96
+ if ( throwException ) throw new Exception ( "bang!" ) ;
97
+ return default ;
98
+ } ;
99
+ await client . StartAsync ( CancellationToken . None ) ;
100
+
101
+ var map = await client . GetMapAsync < int , IGenericRecord > ( "bar" ) ;
102
+ await map . PutAsync ( 1 , GenericRecordBuilder . Compact ( "bar1" ) . Build ( ) ) ;
103
+
104
+ HConsole . WriteLine ( this , "-------- STOP MEMBER --------" ) ;
105
+
106
+ // stop the member, wait until it is actually removed (else we might reconnect to it)
107
+ await RcClient . StopMemberWaitRemovedAsync ( client , RcCluster , member ) . CfAwait ( ) ;
108
+
109
+ // trigger exceptions
110
+ throwException = true ;
111
+ if ( recover )
112
+ {
113
+ async Task RecoverAfter ( int delayMilliseconds )
114
+ {
115
+ await Task . Delay ( delayMilliseconds ) ;
116
+ throwException = false ;
117
+ }
118
+
119
+ var recoverTask = RecoverAfter ( 5000 ) ; // fire-and-forget
120
+ }
121
+
122
+ HConsole . WriteLine ( this , "-------- START MEMBER --------" ) ;
123
+
124
+ // start another member
125
+ member = await RcClient . StartMemberAsync ( RcCluster ) . CfAwait ( ) ;
126
+
127
+ HConsole . WriteLine ( this , "-------- PUT ASYNC --------" ) ;
128
+
129
+ if ( recover )
130
+ {
131
+ // this will eventually complete once we're able to reconnect
132
+ await map . PutAsync ( 2 , GenericRecordBuilder . Compact ( "bar2" ) . Build ( ) ) . CfAwait ( ) ;
133
+ }
134
+ else
135
+ {
136
+ // this will never complete because we'll never be able to reconnect
137
+ //
138
+ // Java: ReconnectMode can be OFF, ON (blocking invocations) or ASYNC (not blocking, triggers HazelcastClientOfflineException)
139
+ // .NET: ReconnectMode can be DoNotReconnect = OFF, ReconnectSync or ReconnectAsync - but these two have the same effect
140
+ //
141
+ // In Java, ASYNC causes any invocation to *immediately* fail with HazelcastClientOfflineException if the client is
142
+ // reconnecting, whereas ON causes the invocation to be retried, and it may eventually fail with OperationTimeoutException.
143
+ //
144
+ // In .NET, invocations are tried (and retried) while the client is reconnecting, until either the client reconnects and
145
+ // the invocation succeeds, or it times out. So, essentially, .NET is ON and it makes sense that we get a TaskTimeoutException
146
+ // below.
147
+
148
+ await AssertEx . ThrowsAsync < TaskTimeoutException > ( async ( ) => await map . PutAsync ( 2 , GenericRecordBuilder . Compact ( "bar2" ) . Build ( ) ) . CfAwait ( ) ) ;
149
+ }
150
+
151
+ HConsole . WriteLine ( this , "-------- STOP MEMBER --------" ) ;
152
+
153
+ await RcClient . StopMemberAsync ( RcCluster , member ) . CfAwait ( ) ;
154
+
155
+ HConsole . WriteLine ( this , "-------- END --------" ) ;
156
+ }
157
+
158
+ [ Test ]
159
+ public async Task ClusterRestart ( )
160
+ {
161
+ //using var _ = UseHConsole();
162
+
163
+ // start a member
164
+ var member = await RcClient . StartMemberAsync ( RcCluster ) . CfAwait ( ) ;
165
+ await using var cleanup = new DisposeAsyncAction ( async ( ) => await RcClient . StopMemberAsync ( RcCluster , member ) ) ;
166
+
167
+ var messages = new List < ClientMessage > ( ) ;
168
+
169
+ // use a clean client + hook into cluster messaging to capture messages (before client starts)
170
+ await using var client = HazelcastClientFactory . CreateClient ( CreateHazelcastOptions ( ) ) ;
171
+ client . Cluster . Messaging . SendingMessage += message =>
172
+ {
173
+ messages . Add ( message ) ;
174
+ return default ;
175
+ } ;
176
+ await client . StartAsync ( CancellationToken . None ) ;
177
+
178
+ var clientMembers = client . Members ;
179
+ Assert . That ( clientMembers . Count , Is . EqualTo ( 1 ) ) ;
180
+ Assert . That ( clientMembers . First ( ) . Member . Id , Is . EqualTo ( Guid . Parse ( member . Uuid ) ) ) ;
181
+ HConsole . WriteLine ( this , $ "CONNECTED TO MEMBER { member . Uuid } ") ;
182
+
183
+ var map = await client . GetMapAsync < int , IGenericRecord > ( "bar" ) ;
184
+
185
+ // add values = will publish the corresponding schemas
186
+ await map . PutAsync ( 1 , GenericRecordBuilder . Compact ( "bar1" ) . Build ( ) ) ;
187
+ await map . PutAsync ( 2 , GenericRecordBuilder . Compact ( "bar2" ) . Build ( ) ) ;
188
+
189
+ // stop the member, wait until it is actually removed (else we might reconnect to it)
190
+ await RcClient . StopMemberWaitRemovedAsync ( client , RcCluster , member ) ;
191
+
192
+ messages . Clear ( ) ;
193
+
194
+ // start another member
195
+ member = await RcClient . StartMemberAsync ( RcCluster ) ;
196
+
197
+ // ensure that, eventually, the client is going to connect to *that* other member
198
+ // and not, because of some timing issues, on the previous one that would not stop
199
+ // fast enough - StopMemberWaitRemoved waits for the client to lose its connection
200
+ // to the member but the remote controller has no wait of notifying us that the
201
+ // member is actually dead for real and not going to accept connections anymore.
202
+ await AssertEx . SucceedsEventually ( ( ) =>
203
+ {
204
+ clientMembers = client . Members ;
205
+ Assert . That ( clientMembers . Count , Is . EqualTo ( 1 ) ) ;
206
+ Assert . That ( clientMembers . First ( ) . Member . Id , Is . EqualTo ( Guid . Parse ( member . Uuid ) ) ) ;
207
+ } , 60_000 , 1_000 ) ;
208
+
209
+ // new member, values are lost
210
+ var value1 = await map . GetAsync ( 1 ) ;
211
+ Assert . That ( value1 , Is . Null ) ;
212
+
213
+ // yet we've been reconnected, schemas have been republished
214
+ Assert . That ( messages . Any ( x => x . MessageType == ClientSendAllSchemasCodec . RequestMessageType ) ) ;
215
+
216
+ await RcClient . StopMemberAsync ( RcCluster , member ) . CfAwait ( ) ;
217
+ }
218
+
219
+ [ Test ]
220
+ public async Task ReadSchemaAfterWrite_withObjectValueType ( )
221
+ {
222
+ //using var _ = UseHConsole();
223
+
224
+ // start a member
225
+ var member = await RcClient . StartMemberAsync ( RcCluster ) . CfAwait ( ) ;
226
+ await using var cleanup = new DisposeAsyncAction ( async ( ) => await RcClient . StopMemberAsync ( RcCluster , member ) ) ;
227
+
228
+ // use a clean client
229
+ await using var client = await CreateAndStartClientAsync ( ) ;
230
+
231
+ var map = await client . GetMapAsync < int , object > ( "bar" ) ;
232
+
233
+ await map . PutAsync ( 1 , GenericRecordBuilder . Compact ( "bar1" ) . Build ( ) ) ;
234
+ var value = await map . PutAsync ( 1 , GenericRecordBuilder . Compact ( "bar2" ) . Build ( ) ) ;
235
+ Assert . That ( value , Is . Not . Null ) ;
236
+ Assert . That ( value , Is . InstanceOf < IGenericRecord > ( ) ) ;
237
+
238
+ await RcClient . StopMemberAsync ( RcCluster , member ) . CfAwait ( ) ;
239
+ }
240
+
241
+ protected override HazelcastOptions CreateHazelcastOptions ( )
242
+ {
243
+ var options = base . CreateHazelcastOptions ( ) ;
244
+ options . Networking . ReconnectMode = ReconnectMode . ReconnectSync ;
245
+ options . Messaging . RetryTimeoutSeconds = 10 ;
246
+ return options ;
247
+ }
248
+
249
+ protected override HazelcastOptionsBuilder CreateHazelcastOptionsBuilder ( )
250
+ {
251
+ return base . CreateHazelcastOptionsBuilder ( ) . WithHConsoleLogger ( ) ;
252
+ }
253
+ }
0 commit comments