1313 */
1414package com .facebook .presto .router .cluster ;
1515
16- import com .facebook .airlift .bootstrap .LifeCycleManager ;
1716import com .facebook .airlift .log .Logger ;
1817import com .facebook .presto .router .RouterConfig ;
1918import com .facebook .presto .router .scheduler .CustomSchedulerManager ;
3433import io .airlift .units .Duration ;
3534import org .weakref .jmx .Managed ;
3635
37- import javax .annotation .PostConstruct ;
36+ import javax .annotation .PreDestroy ;
3837import javax .inject .Inject ;
3938
40- import java .io .File ;
4139import java .io .IOException ;
4240import java .net .URI ;
41+ import java .nio .file .ClosedWatchServiceException ;
4342import java .nio .file .FileSystems ;
4443import java .nio .file .Path ;
45- import java .nio .file .StandardWatchEventKinds ;
44+ import java .nio .file .Paths ;
4645import java .nio .file .WatchEvent ;
4746import java .nio .file .WatchKey ;
4847import java .nio .file .WatchService ;
4948import java .util .HashMap ;
5049import java .util .List ;
5150import java .util .Map ;
5251import java .util .Optional ;
53- import java .util .concurrent .CompletableFuture ;
5452import java .util .concurrent .ConcurrentHashMap ;
5553import java .util .concurrent .ScheduledExecutorService ;
54+ import java .util .concurrent .ScheduledFuture ;
5655import java .util .concurrent .atomic .AtomicBoolean ;
5756import java .util .concurrent .atomic .AtomicReference ;
5857import java .util .stream .Collectors ;
6766import static com .google .common .base .Preconditions .checkArgument ;
6867import static com .google .common .collect .ImmutableList .toImmutableList ;
6968import static com .google .common .collect .ImmutableMap .toImmutableMap ;
69+ import static java .nio .file .StandardWatchEventKinds .ENTRY_CREATE ;
70+ import static java .nio .file .StandardWatchEventKinds .ENTRY_MODIFY ;
7071import static java .util .Objects .requireNonNull ;
7172import static java .util .concurrent .Executors .newSingleThreadScheduledExecutor ;
7273import static java .util .concurrent .TimeUnit .MILLISECONDS ;
74+ import static java .util .concurrent .TimeUnit .SECONDS ;
7375
7476public class ClusterManager
77+ implements AutoCloseable
7578{
7679 private final AtomicReference <ClusterManagerConfig > currentConfig = new AtomicReference <>();
7780
78- public final RouterConfig routerConfig ;
81+ private final Path configFile ;
7982 private final Logger log = Logger .get (ClusterManager .class );
8083
8184 // Cluster status
@@ -84,25 +87,72 @@ public class ClusterManager
8487
8588 private final AtomicBoolean isWatchServiceStarted = new AtomicBoolean ();
8689 private final RemoteInfoFactory remoteInfoFactory ;
87- private final LifeCycleManager lifeCycleManager ;
8890 private final HashMap <String , HashMap <URI , Integer >> serverWeights = new HashMap <>();
8991 private final CustomSchedulerManager schedulerManager ;
92+ private final ScheduledExecutorService scheduledExecutorService ;
93+ private final ScheduledFuture <?> configDetection ;
94+ private final WatchService watchService ;
95+ private final WatchKey watchKey ;
9096
9197 @ Inject
92- public ClusterManager (RouterConfig config , RemoteInfoFactory remoteInfoFactory , LifeCycleManager lifeCycleManager ,
93- CustomSchedulerManager schedulerManager )
98+ public ClusterManager (RouterConfig config , RemoteInfoFactory remoteInfoFactory , CustomSchedulerManager schedulerManager )
99+ throws IOException
94100 {
95- this .routerConfig = requireNonNull (config , "config is null" );
101+ this .configFile = Paths . get ( requireNonNull (config , "config is null" ). getConfigFile () );
96102 this .remoteInfoFactory = requireNonNull (remoteInfoFactory , "remoteInfoFactory is null" );
97- this .lifeCycleManager = requireNonNull (lifeCycleManager , "lifecycleManager is null" );
98103 this .schedulerManager = schedulerManager ;
99- onConfigChangeDetection ();
100- this .initializeServerWeights ();
104+ reloadConfig ();
105+ initializeServerWeights ();
106+ watchService = FileSystems .getDefault ().newWatchService ();
107+ Path parentDir = configFile .getParent ();
108+ log .info ("Router config watch service monitoring %s" , parentDir );
109+ watchKey = parentDir .register (watchService ,
110+ new WatchEvent .Kind [] {ENTRY_CREATE , ENTRY_MODIFY },
111+ SensitivityWatchEventModifier .HIGH );
112+ log .info ("Successfully registered watch service for %s" , parentDir );
113+ scheduledExecutorService = newSingleThreadScheduledExecutor ();
114+ configDetection = scheduledExecutorService .scheduleAtFixedRate (this ::monitorConfig , 0 , 3 , SECONDS );
101115 }
102116
103- protected void onConfigChangeDetection ()
117+ protected void monitorConfig ()
104118 {
105- RouterSpec newRouterSpec = parseRouterConfig (routerConfig )
119+ boolean reload = false ;
120+ try {
121+ WatchKey key = watchService .poll (1 , SECONDS );
122+ if (key == null ) {
123+ return ;
124+ }
125+ List <WatchEvent <?>> events = key .pollEvents ();
126+ log .debug ("Changes to router config directory detected" );
127+ for (WatchEvent <?> event : events ) {
128+ log .debug ("Event detected: %s, path: %s" , event .kind ().name (), event .context ());
129+ Path changed = (Path ) event .context ();
130+ if (changed .endsWith (configFile .getFileName ())) {
131+ reload = true ;
132+ break ;
133+ }
134+ else {
135+ log .debug ("Change to %s ignored by ClusterManager (config file is %s)" , event .context (), configFile );
136+ }
137+ }
138+ key .reset ();
139+ }
140+ catch (ClosedWatchServiceException e ) {
141+ log .warn ("Watch service closed. Future updates configuration changes will not be detected." );
142+ }
143+ catch (InterruptedException e ) {
144+ Thread .currentThread ().interrupt ();
145+ log .warn ("Watch service interrupted while waiting for configuration updates" );
146+ }
147+
148+ if (reload ) {
149+ reloadConfig ();
150+ }
151+ }
152+
153+ protected void reloadConfig ()
154+ {
155+ RouterSpec newRouterSpec = parseRouterConfig (configFile )
106156 .orElseThrow (() -> new PrestoException (CONFIGURATION_INVALID , "Failed to load router config" ));
107157 Map <String , GroupSpec > newGroups = newRouterSpec .getGroups ().stream ().collect (toImmutableMap (GroupSpec ::getName , group -> group ));
108158 List <SelectorRuleSpec > newGroupSelectors = ImmutableList .copyOf (newRouterSpec .getSelectors ());
@@ -132,54 +182,6 @@ protected void onConfigChangeDetection()
132182 currentConfig .set (new ClusterManagerConfig (newGroups , newGroupSelectors , newScheduler , newSchedulerType ));
133183 }
134184
135- @ PostConstruct
136- public void startConfigReloadTaskFileWatcher ()
137- {
138- CompletableFuture .supplyAsync (() -> {
139- try (WatchService watchService = FileSystems .getDefault ().newWatchService ()) {
140- File routerConfigFile = new File (routerConfig .getConfigFile ());
141- log .info ("Router config watch service monitoring %s" , routerConfig .getConfigFile ());
142- Path parentDir = routerConfigFile .toPath ().getParent ();
143- parentDir .register (
144- watchService ,
145- new WatchEvent .Kind [] {
146- StandardWatchEventKinds .ENTRY_MODIFY ,
147- StandardWatchEventKinds .ENTRY_CREATE ,
148- StandardWatchEventKinds .ENTRY_DELETE ,
149- StandardWatchEventKinds .OVERFLOW },
150- SensitivityWatchEventModifier .HIGH );
151- isWatchServiceStarted .set (true );
152- log .info ("Successfully registered watch service for %s" , parentDir );
153-
154- while (true ) {
155- WatchKey key = watchService .take ();
156- log .info ("Changes to router config directory detected: %s" , routerConfigFile );
157- for (WatchEvent <?> event : key .pollEvents ()) {
158- log .debug ("Event detected: %s, path: %s" , event .kind ().name (), event .context ());
159- Path changed = (Path ) event .context ();
160- if (changed .endsWith (routerConfigFile .getName ())) {
161- try {
162- onConfigChangeDetection ();
163- }
164- catch (Exception e ) {
165- log .error ("Exception in config reload" );
166- }
167- }
168- else {
169- log .debug ("Config change to %s ignored by ClusterManager (config file is %s)" , event .context (), routerConfigFile .getName ());
170- }
171- }
172- key .reset ();
173- }
174- }
175- catch (IOException | InterruptedException e ) {
176- log .error ("Exception in file watcher loop while monitoring %s, %s" , routerConfig .getConfigFile (), e );
177- lifeCycleManager .stop ();
178- throw new RuntimeException (e );
179- }
180- });
181- }
182-
183185 public List <URI > getAllClusters ()
184186 {
185187 return currentConfig .get ().getGroups ().values ().stream ()
@@ -199,9 +201,9 @@ public Optional<URI> getDestination(RequestInfo requestInfo)
199201 GroupSpec groupSpec = config .getGroups ().get (target .get ());
200202
201203 List <URI > healthyClusterURIs = groupSpec .getMembers ().stream ().filter ((entry ) ->
202- Optional .ofNullable (remoteClusterInfos .get (entry ))
203- .map (RemoteClusterInfo ::isHealthy )
204- .orElse (false ))
204+ Optional .ofNullable (remoteClusterInfos .get (entry ))
205+ .map (RemoteClusterInfo ::isHealthy )
206+ .orElse (false ))
205207 .collect (Collectors .toList ());
206208
207209 if (healthyClusterURIs .isEmpty ()) {
@@ -285,6 +287,21 @@ public boolean getIsWatchServiceStarted()
285287 return isWatchServiceStarted .get ();
286288 }
287289
290+ @ PreDestroy
291+ @ Override
292+ public void close ()
293+ throws Exception
294+ {
295+ try {
296+ watchKey .cancel ();
297+ watchService .close ();
298+ }
299+ finally {
300+ configDetection .cancel (true );
301+ scheduledExecutorService .shutdownNow ();
302+ }
303+ }
304+
288305 public static class ClusterStatusTracker
289306 {
290307 private final Logger log = Logger .get (ClusterStatusTracker .class );
0 commit comments