Files
Tommaso Fontana b38832e251 Pypeline: move epoch to storage_provider
Move the epoch inside the storage_provider, this allows us to make the
storage_provider_factory to return an async context manager.
This way we can simplify the locking for the daemon, but forces the
CLI project commands to deal with async code.
2025-11-11 17:15:28 +01:00

76 lines
2.8 KiB
Python

#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
import graphlib
import hashlib
from collections.abc import Buffer
from io import BufferedIOBase
from pathlib import Path
from revng.pypeline.object import Kind, ObjectID
from revng.pypeline.utils.registry import get_singleton
__all__ = ("_OBJECTID_MAXSIZE", "check_kind_structure")
# Number of bytes that the biggest ObjectID will contain when serialized to bytes
# Currently fixed to 17 because 1 byte for kind + 16 bytes for MetaAddress
_OBJECTID_MAXSIZE = 17
# Check that the kind structure is smaller than the `_OBJECTID_MAXSIZE`. This
# is a hack to avoid having storage providers be more flexible about the
# structure of Kinds. Currently the storage providers can assume that the revng
# kind structure is the one they are operating on and make stronger assumptions
# and optimizations. If needed this check can be lifted but it would require
# extra work and extra flexibility.
def check_kind_structure():
kind_type = get_singleton(Kind)
kind_root = kind_type.root()
kinds = kind_type.kinds()
# Check that we can store all Kinds in a byte
assert len(kinds) <= 2**8
# Sort kinds
sorter = graphlib.TopologicalSorter()
for kind in kinds:
if (parent := kind.parent()) is not None:
sorter.add(kind, parent)
else:
sorter.add(kind)
# Compute the serialized kind size
kind_sizes: dict[Kind, int] = {}
for kind in sorter.static_order():
if kind == kind_root:
kind_sizes[kind] = 0
else:
kind_sizes[kind] = kind_sizes[kind.parent()] + 1 + kind.byte_size()
# Check that the maximum kind size does not exceed the expected maximum size
assert max(kind_sizes.values()) <= _OBJECTID_MAXSIZE
# Generally SQL-like implementations require, in the general case, to
# dynamically generate the query to invalidate objects from paths. To simplify
# things we rely on the fact that currently objects have a maximum depth of
# 2. This function will be checked by SQL-like StorageProviders when adding
# object dependencies via `add_dependencies`.
def check_object_id_supported_by_sql(object_id: ObjectID):
if object_id.kind().rank() >= 2:
raise NotImplementedError("Objects with rank >= 2 are not supported")
# Compute the hash of the specified object, conformant to the hash
# specification of `FileRequest`
def compute_hash(input_: Path | BufferedIOBase | bytes | Buffer) -> str:
if isinstance(input_, Path):
with open(input_, "rb") as f:
return hashlib.file_digest(f, "sha256").hexdigest()
elif isinstance(input_, BufferedIOBase):
return hashlib.file_digest(input_, "sha256").hexdigest()
elif isinstance(input_, (bytes, Buffer)):
return hashlib.sha256(input_).hexdigest()
else:
raise ValueError(f"Unknown input, {type(input_)}")