diff options
| author | 2022-11-22 23:47:25 -0500 | |
|---|---|---|
| committer | 2022-11-22 23:47:25 -0500 | |
| commit | 8c897dc0e86f4c6cb5e09e376c6aa40d4093ed7d (patch) | |
| tree | 48eeef25eb64eff4f2de19114f9221ad9bf350c5 | |
| parent | Add file send integration tests (diff) | |
Refactor MemFS set and semaphore to rely on fid lock
Also changed None properties to instead raise RuntimeError if MemFS is accessed before __enter__
| -rw-r--r-- | snekbox/memfs.py | 88 | 
1 files changed, 41 insertions, 47 deletions
| diff --git a/snekbox/memfs.py b/snekbox/memfs.py index c5c1505..74b0d70 100644 --- a/snekbox/memfs.py +++ b/snekbox/memfs.py @@ -2,11 +2,10 @@  from __future__ import annotations  import logging -import os  import subprocess  from collections.abc import Generator +from functools import cached_property  from pathlib import Path -from threading import BoundedSemaphore  from types import TracebackType  from typing import Type  from uuid import uuid4 @@ -15,16 +14,11 @@ from snekbox.snekio import FileAttachment  log = logging.getLogger(__name__) -PID = os.getpid() -NAMESPACE_DIR = Path("/memfs") -NAMESPACE_DIR.mkdir(exist_ok=True) - - -def mount_tmpfs(name: str, size: int | str) -> Path: +def mount_tmpfs(path: str | Path, size: int | str) -> Path:      """Create and mount a tmpfs directory.""" -    tmp = NAMESPACE_DIR / name -    tmp.mkdir() +    path = Path(path) +    path.mkdir()      # Mount the tmpfs      subprocess.check_call(          [ @@ -34,18 +28,18 @@ def mount_tmpfs(name: str, size: int | str) -> Path:              "-o",              f"size={size}",              "tmpfs", -            str(tmp), +            str(path),          ]      ) -    return tmp +    return path -def unmount_tmpfs(name: str) -> None: +def unmount_tmpfs(path: str | Path) -> None:      """Unmount and remove a tmpfs directory.""" -    tmp = NAMESPACE_DIR / name -    subprocess.check_call(["umount", str(tmp)]) +    path = Path(path) +    subprocess.check_call(["umount", str(path)])      # Unmounting will not remove the original folder, so do that here -    tmp.rmdir() +    path.rmdir()  def parse_files( @@ -65,39 +59,48 @@ def parse_files(  class MemFS:      """A temporary directory using tmpfs.""" -    assignment_lock = BoundedSemaphore(1) -    assigned_names: set[str] = set()  # Pool of tempdir names in use - -    def __init__(self, instance_size: int) -> None: +    def __init__(self, instance_size: int, root_dir: str | Path = "/memfs") -> None:          """          Create a temporary directory using tmpfs. -        size: Size limit of each tmpfs instance in bytes +        Args: +            instance_size: Size limit of each tmpfs instance in bytes. +            root_dir: Root directory to mount instances in.          """ -        self.path: Path | None = None          self.instance_size = instance_size +        self._path: Path | None = None +        self.root_dir: Path = Path(root_dir) +        self.root_dir.mkdir(exist_ok=True, parents=True) + +    @cached_property +    def path(self) -> Path: +        """Returns the path of the MemFS.""" +        if self._path is None: +            raise RuntimeError("MemFS accessed before __enter__.") +        return self._path      @property      def name(self) -> str | None:          """Name of the temp dir.""" -        return self.path.name if self.path else None +        return self.path.name      @property      def home(self) -> Path | None:          """Path to home directory.""" -        return Path(self.path, "home") if self.path else None +        return Path(self.path, "home")      def __enter__(self) -> MemFS:          """Mounts a new tempfs, returns self.""" -        with self.assignment_lock: -            for _ in range(10): -                # Combine PID to avoid collisions with multiple snekbox processes -                if (name := f"{PID}-{uuid4()}") not in self.assigned_names: -                    self.path = mount_tmpfs(name, self.instance_size) -                    self.assigned_names.add(name) -                    break -            else: -                raise RuntimeError("Failed to generate a unique tempdir name in 10 attempts") +        for _ in range(10): +            name = str(uuid4()) +            try: +                path = self.root_dir / name +                self._path = mount_tmpfs(path, self.instance_size) +                break +            except FileExistsError: +                continue +        else: +            raise RuntimeError("Failed to generate a unique tempdir name in 10 attempts")          self.mkdir("home")          return self @@ -131,20 +134,11 @@ class MemFS:                  yield FileAttachment.from_path(file)      def cleanup(self) -> None: -        """Unmounts tmpfs, releases name.""" -        if self.path is None: +        """Unmounts tmpfs.""" +        if self._path is None:              return -        # Remove the path folder -        unmount_tmpfs(self.name) - -        if not self.path.exists(): -            with self.assignment_lock: -                self.assigned_names.remove(self.name) -        else: -            # Don't remove name from pool if failed to delete folder -            logging.warning(f"Failed to remove {self.path} in cleanup") - -        self.path = None +        unmount_tmpfs(self.path) +        self._path = None      def __repr__(self): -        return f"<MemoryTempDir {self.name or '(Uninitialized)'}>" +        return f"<MemFS {self.name if self._path else '(Uninitialized)'}>" | 
