2
2
3
3
import java .io .IOException ;
4
4
import java .net .InetSocketAddress ;
5
+ import java .net .SocketAddress ;
5
6
import java .nio .channels .SocketChannel ;
7
+ import java .util .ArrayList ;
6
8
import java .util .Arrays ;
9
+ import java .util .Collection ;
10
+ import java .util .Collections ;
11
+ import java .util .List ;
12
+ import java .util .concurrent .atomic .AtomicInteger ;
13
+ import java .util .concurrent .locks .Lock ;
14
+ import java .util .concurrent .locks .ReadWriteLock ;
15
+ import java .util .concurrent .locks .ReentrantReadWriteLock ;
16
+ import java .util .stream .Collectors ;
7
17
8
18
/**
9
19
* Basic reconnection strategy that changes addresses in a round-robin fashion.
10
20
* To be used with {@link TarantoolClientImpl}.
11
21
*/
12
- public class RoundRobinSocketProviderImpl implements SocketChannelProvider {
13
- /** Timeout to establish socket connection with an individual server. */
14
- private int timeout ; // 0 is infinite.
15
- /** Limit of retries. */
16
- private int retriesLimit = -1 ; // No-limit.
17
- /** Server addresses as configured. */
18
- private final String [] addrs ;
19
- /** Socket addresses. */
20
- private final InetSocketAddress [] sockAddrs ;
21
- /** Current position within {@link #sockAddrs} array. */
22
- private int pos ;
22
+ public class RoundRobinSocketProviderImpl extends BaseSocketChannelProvider implements RefreshableSocketProvider {
23
23
24
24
/**
25
- * Constructs an instance.
26
- *
27
- * @param addrs Array of addresses in a form of [host]:[port].
25
+ * Socket addresses.
28
26
*/
29
- public RoundRobinSocketProviderImpl (String ... addrs ) {
30
- if (addrs == null || addrs .length == 0 )
31
- throw new IllegalArgumentException ("addrs is null or empty." );
32
-
33
- this .addrs = Arrays .copyOf (addrs , addrs .length );
27
+ private final List <InetSocketAddress > socketAddresses = new ArrayList <>();
34
28
35
- sockAddrs = new InetSocketAddress [this .addrs .length ];
36
-
37
- for (int i = 0 ; i < this .addrs .length ; i ++) {
38
- sockAddrs [i ] = parseAddress (this .addrs [i ]);
39
- }
40
- }
29
+ /**
30
+ * Current position within {@link #socketAddresses} array.
31
+ */
32
+ private AtomicInteger currentPosition = new AtomicInteger (-1 );
41
33
42
34
/**
43
- * @return Configured addresses in a form of [host]:[port].
35
+ * Lock
44
36
*/
45
- public String [] getAddresses () {
46
- return this .addrs ;
47
- }
37
+ private ReadWriteLock addressListLock = new ReentrantReadWriteLock ();
48
38
49
39
/**
50
- * Sets maximum amount of time to wait for a socket connection establishment
51
- * with an individual server.
52
- *
53
- * Zero means infinite timeout.
40
+ * Constructs an instance.
54
41
*
55
- * @param timeout Timeout value, ms.
56
- * @return {@code this}.
57
- * @throws IllegalArgumentException If timeout is negative.
42
+ * @param addresses Array of addresses in a form of [host]:[port].
58
43
*/
59
- public RoundRobinSocketProviderImpl setTimeout (int timeout ) {
60
- if (timeout < 0 )
61
- throw new IllegalArgumentException ("timeout is negative." );
62
-
63
- this .timeout = timeout ;
44
+ public RoundRobinSocketProviderImpl (String ... addresses ) {
45
+ if (addresses .length == 0 ) {
46
+ throw new IllegalArgumentException ("Addresses list must contain at least one address." );
47
+ }
64
48
65
- return this ;
49
+ updateAddressList ( Arrays . asList ( addresses )) ;
66
50
}
67
51
68
- /**
69
- * @return Maximum amount of time to wait for a socket connection establishment
70
- * with an individual server.
71
- */
72
- public int getTimeout () {
73
- return timeout ;
52
+ private void updateAddressList (Collection <String > addresses ) {
53
+ Lock writeLock = addressListLock .writeLock ();
54
+ writeLock .lock ();
55
+ try {
56
+ InetSocketAddress lastAddress = getLastObtainedAddress ();
57
+ socketAddresses .clear ();
58
+ addresses .stream ()
59
+ .map (this ::parseAddress )
60
+ .collect (Collectors .toCollection (() -> socketAddresses ));
61
+ if (lastAddress != null ) {
62
+ int recoveredPosition = socketAddresses .indexOf (lastAddress );
63
+ currentPosition .set (recoveredPosition );
64
+ } else {
65
+ currentPosition .set (-1 );
66
+ }
67
+ } finally {
68
+ writeLock .unlock ();
69
+ }
74
70
}
75
71
76
72
/**
77
- * Sets maximum amount of reconnect attempts to be made before an exception is raised.
78
- * The retry count is maintained by a {@link #get(int, Throwable)} caller
79
- * when a socket level connection was established.
80
- *
81
- * Negative value means unlimited.
82
- *
83
- * @param retriesLimit Limit of retries to use.
84
- * @return {@code this}.
73
+ * @return resolved socket addresses
85
74
*/
86
- public RoundRobinSocketProviderImpl setRetriesLimit (int retriesLimit ) {
87
- this .retriesLimit = retriesLimit ;
88
-
89
- return this ;
75
+ public List <SocketAddress > getAddresses () {
76
+ Lock readLock = addressListLock .readLock ();
77
+ readLock .lock ();
78
+ try {
79
+ return Collections .unmodifiableList (this .socketAddresses );
80
+ } finally {
81
+ readLock .unlock ();
82
+ }
90
83
}
91
84
92
- /**
93
- * @return Maximum reconnect attempts to make before raising exception.
94
- */
95
- public int getRetriesLimit () {
96
- return retriesLimit ;
85
+ protected InetSocketAddress getLastObtainedAddress () {
86
+ Lock readLock = addressListLock .readLock ();
87
+ readLock .lock ();
88
+ try {
89
+ int index = currentPosition .get ();
90
+ return index >= 0 ? socketAddresses .get (index ) : null ;
91
+ } finally {
92
+ readLock .unlock ();
93
+ }
97
94
}
98
95
99
- /** {@inheritDoc} */
100
96
@ Override
101
- public SocketChannel get (int retryNumber , Throwable lastError ) {
102
- if (areRetriesExhausted (retryNumber )) {
103
- throw new CommunicationException ("Connection retries exceeded." , lastError );
104
- }
97
+ protected SocketChannel doRetry (int retryNumber , Throwable lastError ) {
105
98
int attempts = getAddressCount ();
106
- long deadline = System .currentTimeMillis () + timeout * attempts ;
99
+ // todo: recalc deadline?
100
+ long deadline = System .currentTimeMillis () + getTimeout () * attempts ;
107
101
while (!Thread .currentThread ().isInterrupted ()) {
108
- SocketChannel channel = null ;
109
102
try {
110
- channel = SocketChannel .open ();
111
- InetSocketAddress addr = getNextSocketAddress ();
112
- channel .socket ().connect (addr , timeout );
113
- return channel ;
103
+ return openChannel (getNextSocketAddress ());
114
104
} catch (IOException e ) {
115
- if (channel != null ) {
116
- try {
117
- channel .close ();
118
- } catch (IOException ignored ) {
119
- // No-op.
120
- }
121
- }
122
105
long now = System .currentTimeMillis ();
123
106
if (deadline <= now ) {
124
107
throw new CommunicationException ("Connection time out." , e );
@@ -141,42 +124,33 @@ public SocketChannel get(int retryNumber, Throwable lastError) {
141
124
* @return Number of configured addresses.
142
125
*/
143
126
protected int getAddressCount () {
144
- return sockAddrs .length ;
127
+ Lock readLock = addressListLock .readLock ();
128
+ readLock .lock ();
129
+ try {
130
+ return socketAddresses .size ();
131
+ } finally {
132
+ readLock .unlock ();
133
+ }
145
134
}
146
135
147
136
/**
148
137
* @return Socket address to use for the next reconnection attempt.
149
138
*/
150
139
protected InetSocketAddress getNextSocketAddress () {
151
- InetSocketAddress res = sockAddrs [pos ];
152
- pos = (pos + 1 ) % sockAddrs .length ;
153
- return res ;
154
- }
155
-
156
- /**
157
- * Parse a string address in the form of [host]:[port]
158
- * and builds a socket address.
159
- *
160
- * @param addr Server address.
161
- * @return Socket address.
162
- */
163
- protected InetSocketAddress parseAddress (String addr ) {
164
- int idx = addr .indexOf (':' );
165
- String host = (idx < 0 ) ? addr : addr .substring (0 , idx );
166
- int port = (idx < 0 ) ? 3301 : Integer .parseInt (addr .substring (idx + 1 ));
167
- return new InetSocketAddress (host , port );
140
+ Lock readLock = addressListLock .readLock ();
141
+ readLock .lock ();
142
+ try {
143
+ int position = currentPosition .updateAndGet (i -> (i + 1 ) % socketAddresses .size ());
144
+ return socketAddresses .get (position );
145
+ } finally {
146
+ readLock .unlock ();
147
+ }
168
148
}
169
149
170
- /**
171
- * Provides a decision on whether retries limit is hit.
172
- *
173
- * @param retries Current count of retries.
174
- * @return {@code true} if retries are exhausted.
175
- */
176
- private boolean areRetriesExhausted (int retries ) {
177
- int limit = getRetriesLimit ();
178
- if (limit < 0 )
179
- return false ;
180
- return retries >= limit ;
150
+ public void refreshAddresses (Collection <String > addresses ) {
151
+ if (addresses == null || addresses .isEmpty ()) {
152
+ throw new IllegalArgumentException ("Addresses are null or empty." );
153
+ }
154
+ updateAddressList (addresses );
181
155
}
182
156
}
0 commit comments