18
18
19
19
import java .util .Map ;
20
20
import java .util .concurrent .ConcurrentHashMap ;
21
+ import java .util .concurrent .Executors ;
21
22
import java .util .concurrent .ScheduledExecutorService ;
22
23
import java .util .concurrent .ScheduledFuture ;
24
+ import java .util .concurrent .ThreadFactory ;
23
25
import java .util .concurrent .TimeUnit ;
24
26
25
27
import static com .mongodb .ServerConnectionState .Connecting ;
29
31
import static org .bson .util .Assertions .notNull ;
30
32
31
33
class DefaultServer implements ClusterableServer {
34
+
35
+ private final String clusterId ;
36
+
37
+ private enum HeartbeatFrequency {
38
+ NORMAL {
39
+ @ Override
40
+ long getFrequencyMS (final ServerSettings settings ) {
41
+ return settings .getHeartbeatFrequency (MILLISECONDS );
42
+ }
43
+ },
44
+
45
+ RETRY {
46
+ @ Override
47
+ long getFrequencyMS (final ServerSettings settings ) {
48
+ return settings .getHeartbeatConnectRetryFrequency (MILLISECONDS );
49
+ }
50
+ };
51
+
52
+ abstract long getFrequencyMS (final ServerSettings settings );
53
+ }
54
+
32
55
private final ScheduledExecutorService scheduledExecutorService ;
33
56
private final ServerAddress serverAddress ;
34
57
private final ServerStateNotifier stateNotifier ;
35
- private final ScheduledFuture <?> scheduledFuture ;
36
58
private final PooledConnectionProvider connectionProvider ;
37
59
private final Map <ChangeListener <ServerDescription >, Boolean > changeListeners =
38
60
new ConcurrentHashMap <ChangeListener <ServerDescription >, Boolean >();
@@ -41,22 +63,22 @@ class DefaultServer implements ClusterableServer {
41
63
private volatile ServerDescription description ;
42
64
private volatile boolean isClosed ;
43
65
66
+ private ScheduledFuture <?> scheduledFuture ;
67
+ private HeartbeatFrequency currentFrequency ;
68
+
44
69
public DefaultServer (final ServerAddress serverAddress ,
45
70
final ServerSettings settings ,
46
- final PooledConnectionProvider connectionProvider ,
47
- final ScheduledExecutorService scheduledExecutorService ,
48
- Mongo mongo ) {
71
+ final String clusterId , final PooledConnectionProvider connectionProvider ,
72
+ final Mongo mongo ) {
73
+ this . clusterId = notNull ( "clusterId" , clusterId );
49
74
this .settings = notNull ("settings" , settings );
50
-
51
- this .scheduledExecutorService = notNull ("scheduledExecutorService" , scheduledExecutorService );
52
75
this .serverAddress = notNull ("serverAddress" , serverAddress );
53
76
this .description = ServerDescription .builder ().state (Connecting ).address (serverAddress ).build ();
54
77
serverStateListener = new DefaultServerStateListener ();
55
78
this .stateNotifier = new ServerStateNotifier (serverAddress , serverStateListener ,
56
79
settings .getHeartbeatSocketSettings (), mongo );
57
- this .scheduledFuture = scheduledExecutorService .scheduleAtFixedRate (stateNotifier , 0 ,
58
- settings .getHeartbeatFrequency (MILLISECONDS ),
59
- MILLISECONDS );
80
+ this .scheduledExecutorService = Executors .newSingleThreadScheduledExecutor (new DefaultThreadFactory ());
81
+ setHeartbeat (0 , HeartbeatFrequency .NORMAL );
60
82
this .connectionProvider = connectionProvider ;
61
83
}
62
84
@@ -87,14 +109,15 @@ public void invalidate() {
87
109
serverStateListener .stateChanged (new ChangeEvent <ServerDescription >(description , ServerDescription .builder ()
88
110
.state (Connecting )
89
111
.address (serverAddress ).build ()));
90
- scheduledExecutorService . submit ( stateNotifier );
112
+ setHeartbeat ( 0 , HeartbeatFrequency . RETRY );
91
113
connectionProvider .invalidate ();
92
114
}
93
115
94
116
@ Override
95
117
public void close () {
96
118
if (!isClosed ()) {
97
119
scheduledFuture .cancel (true );
120
+ scheduledExecutorService .shutdownNow ();
98
121
stateNotifier .close ();
99
122
connectionProvider .close ();
100
123
isClosed = true ;
@@ -106,18 +129,44 @@ public boolean isClosed() {
106
129
return isClosed ;
107
130
}
108
131
132
+ private void setHeartbeat (final ChangeEvent <ServerDescription > event ) {
133
+ HeartbeatFrequency heartbeatFrequency = event .getNewValue ().getState () == Unconnected
134
+ ? HeartbeatFrequency .RETRY
135
+ : HeartbeatFrequency .NORMAL ;
136
+ long initialDelay = heartbeatFrequency .getFrequencyMS (settings );
137
+ setHeartbeat (initialDelay , heartbeatFrequency );
138
+ }
139
+
140
+ private synchronized void setHeartbeat (final long initialDelay , final HeartbeatFrequency newFrequency ) {
141
+ if (currentFrequency != newFrequency ) {
142
+ currentFrequency = newFrequency ;
143
+ if (scheduledFuture != null ) {
144
+ scheduledFuture .cancel (false );
145
+ }
146
+ scheduledFuture = scheduledExecutorService .scheduleAtFixedRate (stateNotifier , initialDelay ,
147
+ newFrequency .getFrequencyMS (settings ),
148
+ MILLISECONDS );
149
+ }
150
+ }
151
+
152
+ // Custom thread factory for scheduled executor service that creates daemon threads. Otherwise,
153
+ // applications that neglect to close the MongoClient will not exit.
154
+ class DefaultThreadFactory implements ThreadFactory {
155
+ public Thread newThread (final Runnable runnable ) {
156
+ Thread t = new Thread (runnable , "cluster-" + clusterId + "-" + serverAddress );
157
+ t .setDaemon (true );
158
+ return t ;
159
+ }
160
+ }
161
+
109
162
private final class DefaultServerStateListener implements ChangeListener <ServerDescription > {
110
163
@ Override
111
164
public void stateChanged (final ChangeEvent <ServerDescription > event ) {
112
165
description = event .getNewValue ();
113
166
for (ChangeListener <ServerDescription > listener : changeListeners .keySet ()) {
114
167
listener .stateChanged (event );
115
168
}
116
- if (event .getNewValue ().getState () == Unconnected ) {
117
- scheduledExecutorService .schedule (stateNotifier , settings .getHeartbeatConnectRetryFrequency (MILLISECONDS ),
118
- MILLISECONDS );
119
- }
169
+ setHeartbeat (event );
120
170
}
121
-
122
171
}
123
172
}
0 commit comments