Watchdog API is triggered twice for one time change in the path to watch

135 views Asked by At
import os
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import docker
import json
import configparser

class FileChangeHandler(FileSystemEventHandler):
  def on_modified(self, event):
    if event.is_directory:
        return

    print(f"File {event.src_path} has changed. Restarting containers.")
    # Opening JSON file
    try:
        with open('./Tree/fruits/fruits.json', 'r') as jsonfile:
            content = jsonfile.read()
            print("JSON Content:", content)
        json_object = json.loads(content)
    except Exception as e:
        print(f"Error loading JSON file: {e}")
    jsonfile.close()

    restart_containers(json_object)
def update_config(config_obj, key, new_val):
   for each_section in config_obj.sections():
    for (each_key, each_val) in config_obj.items(each_section):
        if key == each_key:
            print("Matched key, update to config file")
            config_obj.set(each_section, each_key, new_val)
            
   return config_obj

def restart_containers(json_object):
    skippable_container_list = ["syz", "abc", "pgs"]

    # Create a Docker client
    client = docker.from_env()

    # Get a list of all containers (including stopped ones)
    containers = client.containers.list(all=True)

    # Iterate through running containers and retrieve statistics
    for container in containers:
       container_name = container.name
       if container_name not in skippable_container_list:
         if "leaf" == container_name:
            if container.status == 'running':
                print(f"Container {container_name} is currently running. Waiting for it to stop...")

                # Wait for the container to stop
                container.wait()
                
            print(f"Container {container_name} is now stopped. Proceeding to update configuration and   restart...")

            # Read INI file
            config_obj = configparser.ConfigParser()
            config_obj.read("./Tree/leaf/leaf.ini")

            # Loop over key-value in JSON file and update config
            for k, v in json_object.items():
                print(f"Key: {k}")
                print(f"Value: {v}")
                config_obj = update_config(config_obj, k, v)
            
            # Write the configuration file to 'example.ini'
            with open('./Tree/leaf/leaf.ini', 'w') as configfile:
                config_obj.write(configfile)
            
            print(f"Starting container: {container_name}")
            container.start()
if __name__ == "__main__":
   path_to_watch = "./Tree/fruits"
   event_handler = FileChangeHandler()
   observer = Observer()
   observer.schedule(event_handler, path=path_to_watch, recursive=True)
   observer.start()

   try:
      print(f"Watching for changes in {path_to_watch}")
      while True:
        time.sleep(1)
   except KeyboardInterrupt:
      observer.stop()
   observer.join()

/* -> so the above is a python script of mine which is inside Tree and fruits_ is also a file inside Tree and fruits.json is inside fruits.And leaf is a container which has a file leaf.ini. ->so my code is supposed to change in leaf.ini if any changes happens in path_to_watch = "./Tree/fruits" means the fruits.json file and restart the container once. ->It means if the container is idle the and i change in fruits.json file the watch dog will trigger the on_modified function and make change in leaf.ini and restart the container once and if container is running it will wait for it to complete the task and then do modification in leaf.ini and restart the container once. ->But my problem here is for 1 time change in the fruits.json file ,it triggers the on_modified function twice means when change is detected in json file and after updation in the leaf.ini file ,hence leads to restarts the container leaf twice after modification in leaf.ini.But as per my code the the on_modified function should be triggered once only and container should restart once only after the change in leaf.ini.

->I am expecting the leaf container to restart only once after the change in leaf.ini. */

1

There are 1 answers

2
Moziii On

I think that your script might be encountering file modification event twice. It is a common issue with file system watchers like watchdog. This can happen due to the way some operating systems and editors save files – they might write to the file multiple times, or write to a temporary file which is then renamed to the original file name, causing multiple events.

To mitigate this issue, below is high-level descriptions of some solutions:

  • Implement a debounce mechanism that ensures you only act on the first event within a given time frame. This means if multiple file modification events occur within a short period, you treat them as one.

  • Before restarting the container, check if the content of the ve_config_test.ini file has actually changed from what it was before. If there are no changes, don't restart the container.

  • Use a flag or a timestamp to mark when you are processing an event and when the process is complete. If a new event comes in while you are still processing a previous one, you can choose to ignore it or queue it to be processed after the current one is done.

For more details on Docker Restart Policies you can refer to these links:

Beginner's Guide to Docker Restart Policy

How to Use Docker Restart Policies to Keep Containers Running

UPDATE:

I think the issue of the container restarting twice, even after implementing a debounce mechanism, may be due to multiple triggers or long-running operations that overlap with the file modification events. When the container is running and takes minutes to stop, it might be better to implement a queuing system instead of a simple debounce to handle this use case.

Here's a high-level description of how you could modify your script to implement a queuing system:

  • When a file change event occurs, add the restart task to a queue.
  • Have a separate process or thread that manages the queue and executes restart tasks.
  • Ensure that if a restart task is already in progress, new tasks are either discarded or queued for execution after the current task completes.
  • Use a flag or a timestamp to mark the start and end of a restart process, ensuring that no new restarts begin if one is already in progress.