2424import org .elasticsearch .common .component .Lifecycle ;
2525import org .elasticsearch .common .settings .ClusterSettings ;
2626import org .elasticsearch .common .settings .Settings ;
27+ import org .elasticsearch .core .IOUtils ;
28+ import org .elasticsearch .core .Strings ;
2729import org .elasticsearch .core .TimeValue ;
2830import org .elasticsearch .env .BuildVersion ;
2931import org .elasticsearch .env .Environment ;
3941import org .mockito .stubbing .Answer ;
4042
4143import java .io .IOException ;
44+ import java .io .UncheckedIOException ;
45+ import java .nio .file .AtomicMoveNotSupportedException ;
4246import java .nio .file .Files ;
4347import java .nio .file .Path ;
44- import java .nio .file .StandardCopyOption ;
4548import java .util .List ;
4649import java .util .Map ;
4750import java .util .Set ;
5053import java .util .concurrent .atomic .AtomicBoolean ;
5154import java .util .function .Consumer ;
5255
56+ import static java .nio .file .StandardCopyOption .ATOMIC_MOVE ;
57+ import static java .nio .file .StandardCopyOption .REPLACE_EXISTING ;
5358import static org .elasticsearch .node .Node .NODE_NAME_SETTING ;
5459import static org .hamcrest .Matchers .anEmptyMap ;
5560import static org .hamcrest .Matchers .hasEntry ;
@@ -190,9 +195,7 @@ public void testInitialFileWorks() throws Exception {
190195 return null ;
191196 }).when (controller ).process (any (), any (XContentParser .class ), any (), any ());
192197
193- CountDownLatch latch = new CountDownLatch (1 );
194-
195- fileSettingsService .addFileChangedListener (latch ::countDown );
198+ CountDownLatch fileProcessingLatch = new CountDownLatch (1 );
196199
197200 Files .createDirectories (fileSettingsService .watchedFileDir ());
198201 // contents of the JSON don't matter, we just need a file to exist
@@ -202,15 +205,14 @@ public void testInitialFileWorks() throws Exception {
202205 try {
203206 return invocation .callRealMethod ();
204207 } finally {
205- latch .countDown ();
208+ fileProcessingLatch .countDown ();
206209 }
207210 }).when (fileSettingsService ).processFileOnServiceStart ();
208211
209212 fileSettingsService .start ();
210213 fileSettingsService .clusterChanged (new ClusterChangedEvent ("test" , clusterService .state (), ClusterState .EMPTY_STATE ));
211214
212- // wait for listener to be called
213- assertTrue (latch .await (20 , TimeUnit .SECONDS ));
215+ longAwait (fileProcessingLatch );
214216
215217 verify (fileSettingsService , times (1 )).processFileOnServiceStart ();
216218 verify (controller , times (1 )).process (any (), any (XContentParser .class ), eq (ReservedStateVersionCheck .HIGHER_OR_SAME_VERSION ), any ());
@@ -223,40 +225,40 @@ public void testProcessFileChanges() throws Exception {
223225 return null ;
224226 }).when (controller ).process (any (), any (XContentParser .class ), any (), any ());
225227
226- // we get three events: initial clusterChanged event, first write, second write
227- CountDownLatch latch = new CountDownLatch (3 );
228-
229- fileSettingsService .addFileChangedListener (latch ::countDown );
230-
231- Files .createDirectories (fileSettingsService .watchedFileDir ());
232- // contents of the JSON don't matter, we just need a file to exist
233- writeTestFile (fileSettingsService .watchedFile (), "{}" );
234-
228+ CountDownLatch changesOnStartLatch = new CountDownLatch (1 );
235229 doAnswer ((Answer <?>) invocation -> {
236230 try {
237231 return invocation .callRealMethod ();
238232 } finally {
239- latch .countDown ();
233+ changesOnStartLatch .countDown ();
240234 }
241235 }).when (fileSettingsService ).processFileOnServiceStart ();
236+
237+ CountDownLatch changesLatch = new CountDownLatch (1 );
242238 doAnswer ((Answer <?>) invocation -> {
243239 try {
244240 return invocation .callRealMethod ();
245241 } finally {
246- latch .countDown ();
242+ changesLatch .countDown ();
247243 }
248244 }).when (fileSettingsService ).processFileChanges ();
249245
246+ Files .createDirectories (fileSettingsService .watchedFileDir ());
247+ // contents of the JSON don't matter, we just need a file to exist
248+ writeTestFile (fileSettingsService .watchedFile (), "{}" );
249+
250250 fileSettingsService .start ();
251251 fileSettingsService .clusterChanged (new ClusterChangedEvent ("test" , clusterService .state (), ClusterState .EMPTY_STATE ));
252- // second file change; contents still don't matter
253- overwriteTestFile (fileSettingsService .watchedFile (), "{}" );
254252
255- // wait for listener to be called (once for initial processing, once for subsequent update)
256- assertTrue (latch .await (20 , TimeUnit .SECONDS ));
253+ longAwait (changesOnStartLatch );
257254
258255 verify (fileSettingsService , times (1 )).processFileOnServiceStart ();
259256 verify (controller , times (1 )).process (any (), any (XContentParser .class ), eq (ReservedStateVersionCheck .HIGHER_OR_SAME_VERSION ), any ());
257+
258+ // second file change; contents still don't matter
259+ writeTestFile (fileSettingsService .watchedFile (), "[]" );
260+ longAwait (changesLatch );
261+
260262 verify (fileSettingsService , times (1 )).processFileChanges ();
261263 verify (controller , times (1 )).process (any (), any (XContentParser .class ), eq (ReservedStateVersionCheck .HIGHER_VERSION_ONLY ), any ());
262264 }
@@ -295,9 +297,7 @@ public void testStopWorksInMiddleOfProcessing() throws Exception {
295297 // Make some fake settings file to cause the file settings service to process it
296298 writeTestFile (fileSettingsService .watchedFile (), "{}" );
297299
298- // we need to wait a bit, on MacOS it may take up to 10 seconds for the Java watcher service to notice the file,
299- // on Linux is instantaneous. Windows is instantaneous too.
300- assertTrue (processFileLatch .await (30 , TimeUnit .SECONDS ));
300+ longAwait (processFileLatch );
301301
302302 // Stopping the service should interrupt the watcher thread, we should be able to stop
303303 fileSettingsService .stop ();
@@ -352,15 +352,34 @@ public void testHandleSnapshotRestoreResetsMetadata() throws Exception {
352352 }
353353
354354 // helpers
355- private void writeTestFile (Path path , String contents ) throws IOException {
356- Path tempFilePath = createTempFile ();
357- Files .writeString (tempFilePath , contents );
358- Files .move (tempFilePath , path , StandardCopyOption .ATOMIC_MOVE , StandardCopyOption .REPLACE_EXISTING );
355+ private static void writeTestFile (Path path , String contents ) {
356+ Path tempFile = null ;
357+ try {
358+ tempFile = Files .createTempFile (path .getParent (), path .getFileName ().toString (), "tmp" );
359+ Files .writeString (tempFile , contents );
360+
361+ try {
362+ Files .move (tempFile , path , REPLACE_EXISTING , ATOMIC_MOVE );
363+ } catch (AtomicMoveNotSupportedException e ) {
364+ Files .move (tempFile , path , REPLACE_EXISTING );
365+ }
366+ } catch (final IOException e ) {
367+ throw new UncheckedIOException (Strings .format ("could not write file [%s]" , path .toAbsolutePath ()), e );
368+ } finally {
369+ // we are ignoring exceptions here, so we do not need handle whether or not tempFile was initialized nor if the file exists
370+ IOUtils .deleteFilesIgnoringExceptions (tempFile );
371+ }
359372 }
360373
361- private void overwriteTestFile (Path path , String contents ) throws IOException {
362- Path tempFilePath = createTempFile ();
363- Files .writeString (tempFilePath , contents );
364- Files .move (tempFilePath , path , StandardCopyOption .REPLACE_EXISTING );
374+ // this waits for up to 20 seconds to account for watcher service differences between OSes
375+ // on MacOS it may take up to 10 seconds for the Java watcher service to notice the file,
376+ // on Linux is instantaneous. Windows is instantaneous too.
377+ private static void longAwait (CountDownLatch latch ) {
378+ try {
379+ assertTrue ("longAwait: CountDownLatch did not reach zero within the timeout" , latch .await (20 , TimeUnit .SECONDS ));
380+ } catch (InterruptedException e ) {
381+ Thread .currentThread ().interrupt ();
382+ fail (e , "longAwait: interrupted waiting for CountDownLatch to reach zero" );
383+ }
365384 }
366385}
0 commit comments