20
20
21
21
import java .util .Collections ;
22
22
import java .util .Comparator ;
23
+ import java .util .HashSet ;
24
+ import java .util .List ;
23
25
import java .util .Set ;
26
+ import java .util .concurrent .atomic .AtomicLong ;
24
27
25
28
import org .neo4j .driver .internal .net .BoltServerAddress ;
26
29
import org .neo4j .driver .internal .security .SecurityPlan ;
27
30
import org .neo4j .driver .internal .spi .Connection ;
28
31
import org .neo4j .driver .internal .spi .ConnectionPool ;
32
+ import org .neo4j .driver .internal .util .Clock ;
29
33
import org .neo4j .driver .internal .util .ConcurrentRoundRobinSet ;
30
34
import org .neo4j .driver .internal .util .Consumer ;
31
35
import org .neo4j .driver .v1 .AccessMode ;
34
38
import org .neo4j .driver .v1 .Record ;
35
39
import org .neo4j .driver .v1 .Session ;
36
40
import org .neo4j .driver .v1 .StatementResult ;
41
+ import org .neo4j .driver .v1 .Value ;
37
42
import org .neo4j .driver .v1 .exceptions .ClientException ;
38
43
import org .neo4j .driver .v1 .exceptions .ConnectionFailureException ;
39
44
import org .neo4j .driver .v1 .exceptions .ServiceUnavailableException ;
40
45
import org .neo4j .driver .v1 .util .BiFunction ;
46
+ import org .neo4j .driver .v1 .util .Function ;
41
47
42
48
import static java .lang .String .format ;
43
49
44
50
public class ClusterDriver extends BaseDriver
45
51
{
46
52
private static final String GET_SERVERS = "dbms.cluster.routing.getServers" ;
53
+ private static final long MAX_TTL = Long .MAX_VALUE / 1000L ;
47
54
private final static Comparator <BoltServerAddress > COMPARATOR = new Comparator <BoltServerAddress >()
48
55
{
49
56
@ Override
50
57
public int compare ( BoltServerAddress o1 , BoltServerAddress o2 )
51
58
{
52
59
int compare = o1 .host ().compareTo ( o2 .host () );
53
- if (compare == 0 )
60
+ if ( compare == 0 )
54
61
{
55
62
compare = Integer .compare ( o1 .port (), o2 .port () );
56
63
}
57
64
58
65
return compare ;
59
66
}
60
67
};
61
- private static final int MIN_SERVERS = 2 ;
68
+ private static final int MIN_SERVERS = 1 ;
62
69
private final ConnectionPool connections ;
63
- private final BiFunction <Connection ,Logger , Session > sessionProvider ;
64
-
65
- private final ConcurrentRoundRobinSet <BoltServerAddress > routingServers = new ConcurrentRoundRobinSet <>(COMPARATOR );
66
- private final ConcurrentRoundRobinSet <BoltServerAddress > readServers = new ConcurrentRoundRobinSet <>(COMPARATOR );
67
- private final ConcurrentRoundRobinSet <BoltServerAddress > writeServers = new ConcurrentRoundRobinSet <>(COMPARATOR );
70
+ private final BiFunction <Connection ,Logger ,Session > sessionProvider ;
71
+ private final Clock clock ;
72
+ private final ConcurrentRoundRobinSet <BoltServerAddress > routingServers =
73
+ new ConcurrentRoundRobinSet <>( COMPARATOR );
74
+ private final ConcurrentRoundRobinSet <BoltServerAddress > readServers = new ConcurrentRoundRobinSet <>( COMPARATOR );
75
+ private final ConcurrentRoundRobinSet <BoltServerAddress > writeServers = new ConcurrentRoundRobinSet <>( COMPARATOR );
76
+ private final AtomicLong expires = new AtomicLong ( 0L );
68
77
69
78
public ClusterDriver ( BoltServerAddress seedAddress ,
70
79
ConnectionPool connections ,
71
80
SecurityPlan securityPlan ,
72
- BiFunction <Connection ,Logger , Session > sessionProvider ,
81
+ BiFunction <Connection ,Logger ,Session > sessionProvider ,
82
+ Clock clock ,
73
83
Logging logging )
74
84
{
75
85
super ( securityPlan , logging );
76
86
routingServers .add ( seedAddress );
77
87
this .connections = connections ;
78
88
this .sessionProvider = sessionProvider ;
89
+ this .clock = clock ;
79
90
checkServers ();
80
91
}
81
92
82
93
private void checkServers ()
83
94
{
84
95
synchronized ( routingServers )
85
96
{
86
- if ( routingServers .size () < MIN_SERVERS ||
97
+ if ( expires .get () < clock .millis () ||
98
+ routingServers .size () < MIN_SERVERS ||
87
99
readServers .isEmpty () ||
88
- writeServers .isEmpty ())
100
+ writeServers .isEmpty () )
89
101
{
90
102
getServers ();
91
103
}
92
104
}
93
105
}
94
106
107
+ private Set <BoltServerAddress > forgetAllServers ()
108
+ {
109
+ final Set <BoltServerAddress > seen = new HashSet <>();
110
+ seen .addAll ( routingServers );
111
+ seen .addAll ( readServers );
112
+ seen .addAll ( writeServers );
113
+ routingServers .clear ();
114
+ readServers .clear ();
115
+ writeServers .clear ();
116
+ return seen ;
117
+ }
118
+
119
+ private long calculateNewExpiry ( Record record )
120
+ {
121
+ long ttl = record .get ( "ttl" ).asLong ();
122
+ long nextExpiry = clock .millis () + 1000L * ttl ;
123
+ if ( ttl < 0 || ttl >= MAX_TTL || nextExpiry < 0 )
124
+ {
125
+ return Long .MAX_VALUE ;
126
+ }
127
+ else
128
+ {
129
+ return nextExpiry ;
130
+ }
131
+ }
132
+
95
133
//must be called from a synchronized block
96
134
private void getServers ()
97
135
{
98
136
BoltServerAddress address = null ;
99
137
try
100
138
{
101
139
boolean success = false ;
102
- while ( !routingServers .isEmpty () && !success )
140
+
141
+ ConcurrentRoundRobinSet <BoltServerAddress > routers = new ConcurrentRoundRobinSet <>( routingServers );
142
+ final Set <BoltServerAddress > seen = forgetAllServers ();
143
+ while ( !routers .isEmpty () && !success )
103
144
{
104
- address = routingServers .hop ();
145
+ address = routers .hop ();
105
146
success = call ( address , GET_SERVERS , new Consumer <Record >()
106
147
{
107
148
@ Override
108
149
public void accept ( Record record )
109
150
{
110
- BoltServerAddress newAddress = new BoltServerAddress ( record .get ( "address" ).asString () );
111
- switch ( record .get ( "mode" ).asString ().toUpperCase () )
151
+ expires .set ( calculateNewExpiry ( record ) );
152
+ List <ServerInfo > servers = servers ( record );
153
+ for ( ServerInfo server : servers )
112
154
{
113
- case "READ" :
114
- readServers .add ( newAddress );
115
- break ;
116
- case "WRITE" :
117
- writeServers .add ( newAddress );
118
- break ;
119
- case "ROUTE" :
120
- routingServers .add ( newAddress );
121
- break ;
155
+ seen .removeAll ( server .addresses () );
156
+ switch ( server .role () )
157
+ {
158
+ case "READ" :
159
+ readServers .addAll ( server .addresses () );
160
+ break ;
161
+ case "WRITE" :
162
+ writeServers .addAll ( server .addresses () );
163
+ break ;
164
+ case "ROUTE" :
165
+ routingServers .addAll ( server .addresses () );
166
+ break ;
167
+ }
122
168
}
123
169
}
124
170
} );
@@ -127,6 +173,12 @@ public void accept( Record record )
127
173
{
128
174
throw new ServiceUnavailableException ( "Run out of servers" );
129
175
}
176
+
177
+ //the server no longer think we should care about these
178
+ for ( BoltServerAddress remove : seen )
179
+ {
180
+ connections .purge ( remove );
181
+ }
130
182
}
131
183
catch ( ClientException ex )
132
184
{
@@ -137,7 +189,7 @@ public void accept( Record record )
137
189
this .close ();
138
190
throw new ServiceUnavailableException (
139
191
String .format ( "Server %s couldn't perform discovery" ,
140
- address == null ? "`UNKNOWN`" : address .toString ()), ex );
192
+ address == null ? "`UNKNOWN`" : address .toString () ), ex );
141
193
}
142
194
else
143
195
{
@@ -146,14 +198,55 @@ public void accept( Record record )
146
198
}
147
199
}
148
200
201
+ private static class ServerInfo
202
+ {
203
+ private final List <BoltServerAddress > addresses ;
204
+ private final String role ;
205
+
206
+ public ServerInfo ( List <BoltServerAddress > addresses , String role )
207
+ {
208
+ this .addresses = addresses ;
209
+ this .role = role ;
210
+ }
211
+
212
+ public String role ()
213
+ {
214
+ return role ;
215
+ }
216
+
217
+ List <BoltServerAddress > addresses ()
218
+ {
219
+ return addresses ;
220
+ }
221
+ }
222
+
223
+ private List <ServerInfo > servers ( Record record )
224
+ {
225
+ return record .get ( "servers" ).asList ( new Function <Value ,ServerInfo >()
226
+ {
227
+ @ Override
228
+ public ServerInfo apply ( Value value )
229
+ {
230
+ return new ServerInfo ( value .get ( "addresses" ).asList ( new Function <Value ,BoltServerAddress >()
231
+ {
232
+ @ Override
233
+ public BoltServerAddress apply ( Value value )
234
+ {
235
+ return new BoltServerAddress ( value .asString () );
236
+ }
237
+ } ), value .get ( "role" ).asString () );
238
+ }
239
+ } );
240
+ }
241
+
149
242
//must be called from a synchronized method
150
243
private boolean call ( BoltServerAddress address , String procedureName , Consumer <Record > recorder )
151
244
{
152
245
Connection acquire = null ;
153
246
Session session = null ;
154
247
try
155
248
{
156
- acquire = connections .acquire (address );
249
+ acquire = connections .acquire ( address );
157
250
session = sessionProvider .apply ( acquire , log );
158
251
159
252
StatementResult records = session .run ( format ( "CALL %s" , procedureName ) );
@@ -217,19 +310,19 @@ public void onWriteFailure( BoltServerAddress address )
217
310
log );
218
311
}
219
312
220
- private Connection acquireConnection ( AccessMode mode )
313
+ private Connection acquireConnection ( AccessMode role )
221
314
{
222
315
//Potentially rediscover servers if we are not happy with our current knowledge
223
316
checkServers ();
224
317
225
- switch ( mode )
318
+ switch ( role )
226
319
{
227
320
case READ :
228
321
return connections .acquire ( readServers .hop () );
229
322
case WRITE :
230
323
return connections .acquire ( writeServers .hop () );
231
324
default :
232
- throw new ClientException ( mode + " is not supported for creating new sessions" );
325
+ throw new ClientException ( role + " is not supported for creating new sessions" );
233
326
}
234
327
}
235
328
@@ -255,13 +348,13 @@ Set<BoltServerAddress> routingServers()
255
348
//For testing
256
349
Set <BoltServerAddress > readServers ()
257
350
{
258
- return Collections .unmodifiableSet (readServers );
351
+ return Collections .unmodifiableSet ( readServers );
259
352
}
260
353
261
354
//For testing
262
355
Set <BoltServerAddress > writeServers ()
263
356
{
264
- return Collections .unmodifiableSet ( writeServers );
357
+ return Collections .unmodifiableSet ( writeServers );
265
358
}
266
359
267
360
//For testing
0 commit comments