Просмотр исходного кода

fuse2: implement lazy children dict allocation for DirEntry

Thomas Waldmann 1 месяц назад
Родитель
Сommit
3393c0f2b7
1 измененных файлов с 53 добавлено и 27 удалено
  1. 53 27
      src/borg/fuse2.py

+ 53 - 27
src/borg/fuse2.py

@@ -45,7 +45,31 @@ class DirEntry:
     def __init__(self, ino, parent=None):
         self.ino = ino  # inode number
         self.parent = parent
-        self.children = {}  # name (bytes) -> DirEntry
+        self.children = None  # name (bytes) -> DirEntry, lazily allocated
+
+    def add_child(self, name, child):
+        """Add a child entry, lazily allocating the children dict if needed."""
+        if self.children is None:
+            self.children = {}
+        self.children[name] = child
+
+    def get_child(self, name):
+        """Get a child entry by name, returns None if not found."""
+        if self.children is None:
+            return None
+        return self.children.get(name)
+
+    def has_child(self, name):
+        """Check if a child with the given name exists."""
+        if self.children is None:
+            return False
+        return name in self.children
+
+    def iter_children(self):
+        """Iterate over (name, child) pairs."""
+        if self.children is None:
+            return iter([])
+        return self.children.items()
 
 
 class FuseBackend:
@@ -135,7 +159,7 @@ class FuseBackend:
                 item.mtime = int(archive.ts.timestamp() * 1e9)
                 self.set_inode(archive_node.ino, item)
 
-                self.root.children[name_bytes] = archive_node
+                self.root.add_child(name_bytes, archive_node)
                 self.pending_archives[archive_node] = archive
 
     def check_pending_archive(self, node):
@@ -183,23 +207,23 @@ class FuseBackend:
                 node = root_node
                 # Traverse/Create directories
                 for segment in segments[:-1]:
-                    if segment not in node.children:
+                    if not node.has_child(segment):
                         new_node = self._create_node(parent=node)
                         # We might need a default directory item if it's an implicit directory
                         self.set_inode(new_node.ino, Item(internal_dict=self.default_dir.as_dict()))
-                        node.children[segment] = new_node
-                    node = node.children[segment]
+                        node.add_child(segment, new_node)
+                    node = node.get_child(segment)
 
                 # Leaf (file or explicit directory)
                 leaf_name = segments[-1]
-                if leaf_name in node.children:
+                if node.has_child(leaf_name):
                     # Already exists (e.g. implicit dir became explicit)
-                    child = node.children[leaf_name]
+                    child = node.get_child(leaf_name)
                     self.set_inode(child.ino, item)  # Update item
                     node = child
                 else:
                     new_node = self._create_node(item, parent=node)
-                    node.children[leaf_name] = new_node
+                    node.add_child(leaf_name, new_node)
                     node = new_node
 
                 # Handle hardlinks (non-versions mode)
@@ -251,20 +275,20 @@ class FuseBackend:
         # Navigate to parent directory
         node = root_node
         for segment in segments[:-1]:
-            if segment not in node.children:
+            if not node.has_child(segment):
                 new_node = self._create_node(parent=node)
                 self.set_inode(new_node.ino, Item(internal_dict=self.default_dir.as_dict()))
-                node.children[segment] = new_node
-            node = node.children[segment]
+                node.add_child(segment, new_node)
+            node = node.get_child(segment)
 
         # Create intermediate directory with the filename
         leaf_name = segments[-1]
-        if leaf_name not in node.children:
+        if not node.has_child(leaf_name):
             intermediate_node = self._create_node(parent=node)
             self.set_inode(intermediate_node.ino, Item(internal_dict=self.default_dir.as_dict()))
-            node.children[leaf_name] = intermediate_node
+            node.add_child(leaf_name, intermediate_node)
         else:
-            intermediate_node = node.children[leaf_name]
+            intermediate_node = node.get_child(leaf_name)
 
         # Create versioned filename
         if version is not None:
@@ -279,18 +303,18 @@ class FuseBackend:
                     # Navigate to the link target
                     target_node = root_node
                     for seg in link_segments[:-1]:
-                        if seg in target_node.children:
-                            target_node = target_node.children[seg]
+                        if target_node.has_child(seg):
+                            target_node = target_node.get_child(seg)
                         else:
                             break
                     else:
                         # Get intermediate dir
                         link_leaf = link_segments[-1]
-                        if link_leaf in target_node.children:
-                            target_intermediate = target_node.children[link_leaf]
+                        if target_node.has_child(link_leaf):
+                            target_intermediate = target_node.get_child(link_leaf)
                             target_versioned = self._make_versioned_name(link_leaf, link_version)
-                            if target_versioned in target_intermediate.children:
-                                original_node = target_intermediate.children[target_versioned]
+                            if target_intermediate.has_child(target_versioned):
+                                original_node = target_intermediate.get_child(target_versioned)
                                 # Create new node but reuse the ID and item from original
                                 item = self.get_inode(original_node.ino)
                                 file_node = self._create_node(item, parent=intermediate_node)
@@ -301,12 +325,12 @@ class FuseBackend:
                                     item.nlink = 1
                                 item.nlink += 1
                                 self.set_inode(file_node.ino, item)
-                                intermediate_node.children[versioned_name] = file_node
+                                intermediate_node.add_child(versioned_name, file_node)
                                 return
 
             # Not a hardlink or first occurrence - create new node
             file_node = self._create_node(item, parent=intermediate_node)
-            intermediate_node.children[versioned_name] = file_node
+            intermediate_node.add_child(versioned_name, file_node)
 
     def _file_version(self, item, path):
         """Calculate version number for a file based on its contents"""
@@ -339,8 +363,9 @@ class FuseBackend:
         segments = path.split(b"/")
         node = root
         for segment in segments:
-            if segment in node.children:
-                node = node.children[segment]
+            child = node.get_child(segment)
+            if child is not None:
+                node = child
             else:
                 return None
         return node
@@ -358,8 +383,9 @@ class FuseBackend:
         for segment in segments:
             if node in self.pending_archives:
                 self.check_pending_archive(node)
-            if segment in node.children:
-                node = node.children[segment]
+            child = node.get_child(segment)
+            if child is not None:
+                node = child
             else:
                 return None
 
@@ -639,7 +665,7 @@ class borgfs(mfuse.Operations, FuseBackend):
         yield ("..", self._make_stat_dict(parent), offset)
         offset += 1
 
-        for name, child_node in node.children.items():
+        for name, child_node in node.iter_children():
             name_str = name.decode("utf-8", "surrogateescape")
             st = self._make_stat_dict(child_node)
             debug_log(f"readdir yielding {name_str} {offset} {st}")