BGPCEP-770: Lock file before processing 04/69804/8
authorClaudio D. Gasparini <claudio.gasparini@pantheon.tech>
Thu, 22 Mar 2018 11:24:10 +0000 (12:24 +0100)
committerClaudio David Gasparini <claudio.gasparini@pantheon.tech>
Fri, 23 Mar 2018 18:24:29 +0000 (18:24 +0000)
In this way we ensure is not still been modified
when event is raised.

Change-Id: I69047f244e78bdf4536d6f2a83d3b80c45191e90
Signed-off-by: Claudio D. Gasparini <claudio.gasparini@pantheon.tech>
bgp/config-example/src/main/resources/initial/network-topology-config.xml
config-loader/config-loader-impl/src/main/java/org/opendaylight/bgpcep/config/loader/impl/ConfigLoaderImpl.java
config-loader/config-loader-impl/src/test/java/org/opendaylight/bgpcep/config/loader/impl/ConfigLoaderImplTest.java

index 3d24d09493e653665076e422d754d8b27798573a..0d107535d098180d195d42ad0822337cc186a87d 100644 (file)
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
   ~ Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
   ~
   ~ This program and the accompanying materials are made available under the
index 9199d78b9596932b9903e4542e03a4fef49a0089..89882658b4da55db30996711d79f1291ac5ca737 100644 (file)
@@ -10,18 +10,25 @@ package org.opendaylight.bgpcep.config.loader.impl;
 
 import static java.util.Objects.requireNonNull;
 
+import com.google.common.base.Stopwatch;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.RandomAccessFile;
 import java.net.URISyntaxException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
 import java.nio.file.ClosedWatchServiceException;
-import java.nio.file.WatchEvent;
 import java.nio.file.WatchKey;
 import java.nio.file.WatchService;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import javax.annotation.concurrent.GuardedBy;
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.stream.XMLInputFactory;
@@ -48,12 +55,15 @@ public final class ConfigLoaderImpl implements ConfigLoader, AutoCloseable {
     private static final String INTERRUPTED = "InterruptedException";
     private static final String EXTENSION = "-.*\\.xml";
     private static final String INITIAL = "^";
+    private static final String READ = "rw";
+    private static final long TIMEOUT_SECONDS = 5;
     @GuardedBy("this")
     private final Map<String, ConfigFileProcessor> configServices = new HashMap<>();
     private final SchemaContext schemaContext;
     private final BindingNormalizedNodeSerializer bindingSerializer;
     private final String path;
     private final Thread watcherThread;
+    private final File file;
     @GuardedBy("this")
     private boolean closed = false;
 
@@ -62,6 +72,7 @@ public final class ConfigLoaderImpl implements ConfigLoader, AutoCloseable {
         this.schemaContext = requireNonNull(schemaContext);
         this.bindingSerializer = requireNonNull(bindingSerializer);
         this.path = requireNonNull(fileWatcher.getPathFile());
+        this.file = new File(this.path);
         this.watcherThread = new Thread(new ConfigLoaderImplRunnable(requireNonNull(fileWatcher.getWatchService())));
     }
 
@@ -87,7 +98,27 @@ public final class ConfigLoaderImpl implements ConfigLoader, AutoCloseable {
         final NormalizedNodeResult result = new NormalizedNodeResult();
         final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
 
-        try (InputStream resourceAsStream = new FileInputStream(new File(this.path, filename))) {
+        final File newFile = new File(this.path, filename);
+        FileChannel channel = new RandomAccessFile(newFile, READ).getChannel();
+
+        FileLock lock = null;
+        final Stopwatch stopwatch = Stopwatch.createStarted();
+        while (lock == null || stopwatch.elapsed(TimeUnit.SECONDS) > TIMEOUT_SECONDS) {
+            try {
+                lock = channel.tryLock();
+            } catch (final IllegalStateException e) {
+                //Ignore
+            }
+            if (lock == null) {
+                try {
+                    Thread.sleep(100L);
+                } catch (InterruptedException e) {
+                    LOG.warn("Failed to lock xml", e);
+                }
+            }
+        }
+
+        try (InputStream resourceAsStream = new FileInputStream(newFile)) {
             final XMLInputFactory factory = XMLInputFactory.newInstance();
             final XMLStreamReader reader = factory.createXMLStreamReader(resourceAsStream);
 
@@ -100,6 +131,7 @@ public final class ConfigLoaderImpl implements ConfigLoader, AutoCloseable {
                 LOG.warn("Failed to parse xml", e);
             } finally {
                 reader.close();
+                channel.close();
             }
         }
 
@@ -111,18 +143,21 @@ public final class ConfigLoaderImpl implements ConfigLoader, AutoCloseable {
         final String pattern = INITIAL + config.getSchemaPath().getLastComponent().getLocalName() + EXTENSION;
         this.configServices.put(pattern, config);
 
-        final File[] fList = new File(this.path).listFiles();
+        final File[] fList = this.file.listFiles();
+        final List<String> newFiles = new ArrayList<>();
         if (fList != null) {
-            for (final File file : fList) {
-                if (file.isFile()) {
-                    final String filename = file.getName();
+            for (final File newFile : fList) {
+                if (newFile.isFile()) {
+                    final String filename = newFile.getName();
                     if (Pattern.matches(pattern, filename)) {
-                        handleConfigFile(config, filename);
+                        newFiles.add(filename);
                     }
                 }
             }
         }
-
+        for (final String filename : newFiles) {
+            handleConfigFile(config, filename);
+        }
         return new AbstractRegistration() {
             @Override
             protected void removeRegistration() {
@@ -140,7 +175,7 @@ public final class ConfigLoaderImpl implements ConfigLoader, AutoCloseable {
 
 
     @Override
-    public synchronized void close() throws Exception {
+    public synchronized void close() {
         LOG.info("Config Loader service closed");
         this.closed = true;
         this.watcherThread.interrupt();
@@ -174,9 +209,11 @@ public final class ConfigLoaderImpl implements ConfigLoader, AutoCloseable {
             }
 
             if (key != null) {
-                for (final WatchEvent<?> event : key.pollEvents()) {
-                    handleEvent(event.context().toString());
-                }
+                final List<String> fileNames = key.pollEvents()
+                        .stream().map(event -> event.context().toString())
+                        .collect(Collectors.toList());
+                fileNames.forEach(this::handleEvent);
+
                 final boolean reset = key.reset();
                 if (!reset) {
                     LOG.warn("Could not reset the watch key.");
index ae7dee5f1b0c3a1cd55a3b771c78b1c4f9c6436c..88e18bd0201cea94025fb4924522ce4ebc0ee8d5 100644 (file)
@@ -39,8 +39,9 @@ public class ConfigLoaderImplTest extends AbstractConfigLoader {
     }
 
     @Test
-    public void configLoaderImplTest() throws Exception {
-        assertNotNull(ClassLoader.getSystemClassLoader().getResource("etc/opendaylight/bgpcep/protocols-config.xml"));
+    public void configLoaderImplTest() {
+        assertNotNull(ClassLoader.getSystemClassLoader()
+                .getResource("etc/opendaylight/bgpcep/protocols-config.xml"));
         final AbstractRegistration ticket = this.configLoader.registerConfigFile(this.processor);
         verify(this.processor).loadConfiguration(any());