1.0rc3: LineRequest
This commit is contained in:
		
							parent
							
								
									04e8ba559e
								
							
						
					
					
						commit
						af90b9c9c6
					
				@ -9,7 +9,7 @@
 | 
				
			|||||||
project = 'ptvp35'
 | 
					project = 'ptvp35'
 | 
				
			||||||
copyright = '2022, PARRRATE TNV'
 | 
					copyright = '2022, PARRRATE TNV'
 | 
				
			||||||
author = 'PARRRATE TNV'
 | 
					author = 'PARRRATE TNV'
 | 
				
			||||||
release = '1.0rc2'
 | 
					release = '1.0rc3'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# -- General configuration ---------------------------------------------------
 | 
					# -- General configuration ---------------------------------------------------
 | 
				
			||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
 | 
					# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										36
									
								
								docs/source/guarantees.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								docs/source/guarantees.rst
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					Guarantees
 | 
				
			||||||
 | 
					==========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MMDB-level guarantees
 | 
				
			||||||
 | 
					---------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* All 0L writes change MMDB instantly.
 | 
				
			||||||
 | 
					* Transaction write works as one atomic write.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Queue-level guarantees
 | 
				
			||||||
 | 
					----------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This level is the mediator between MMDB and filesystem levels. This level includes the pre-write compression buffer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Queue requests occur in the same orders as they were acted upon at MMDB level and in the same order as they will be acted upon at filesystem level.
 | 
				
			||||||
 | 
					* DB can't close until all requests are done.
 | 
				
			||||||
 | 
					* No request can crash the task.
 | 
				
			||||||
 | 
					* Every request eventually succeedes or fails (except for the case of runtime termination).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Buffer-specific guarantees:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* If buffer dump was ever requested, then this request (queued or indirect) will eventually be satisfied.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Filesystem-level guarantees
 | 
				
			||||||
 | 
					---------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* If main file exists without .recover or .truncate_flag, then it's valid.
 | 
				
			||||||
 | 
					* If .recover file is present, then .backup is valid.
 | 
				
			||||||
 | 
					* If .truncate_flag is present, then .truncate is valid and first :code:`.truncate_target()` (contents of .truncate) characters of main file are valid.
 | 
				
			||||||
 | 
					* Every write is final and irreversible (can't be reversed unintentionally due to termination), otherwise it's not considered done. That's achieved using :code:`os.fsync`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Performance guarantees
 | 
				
			||||||
 | 
					----------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* No normal sync methods (except for :code:`io2db`/:code:`db2io` if they're supplied with blocking :code:`IO`) block on IO. Other methods are explicitly marked with postfix :code:`_sync`.
 | 
				
			||||||
 | 
					* All requests are resolved as soon as their conditions are met.
 | 
				
			||||||
@ -1,8 +1,3 @@
 | 
				
			|||||||
.. ptvp35 documentation master file, created by
 | 
					 | 
				
			||||||
   sphinx-quickstart on Sat Nov 19 20:02:24 2022.
 | 
					 | 
				
			||||||
   You can adapt this file completely to your liking, but it should at least
 | 
					 | 
				
			||||||
   contain the root `toctree` directive.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Welcome to ptvp35's documentation!
 | 
					Welcome to ptvp35's documentation!
 | 
				
			||||||
==================================
 | 
					==================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -12,6 +7,7 @@ Welcome to ptvp35's documentation!
 | 
				
			|||||||
   motivation
 | 
					   motivation
 | 
				
			||||||
   usage
 | 
					   usage
 | 
				
			||||||
   modules
 | 
					   modules
 | 
				
			||||||
 | 
					   guarantees
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -20,6 +16,7 @@ Indices and tables
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
* :doc:`motivation`
 | 
					* :doc:`motivation`
 | 
				
			||||||
* :doc:`usage`
 | 
					* :doc:`usage`
 | 
				
			||||||
 | 
					* :doc:`guarantees`
 | 
				
			||||||
* :ref:`genindex`
 | 
					* :ref:`genindex`
 | 
				
			||||||
* :ref:`modindex`
 | 
					* :ref:`modindex`
 | 
				
			||||||
* :ref:`search`
 | 
					* :ref:`search`
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
Motivation
 | 
					Motivation
 | 
				
			||||||
==========
 | 
					==========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This page describes reasons for certain design decisions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
General structure
 | 
					General structure
 | 
				
			||||||
-----------------
 | 
					-----------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ __all__ = (
 | 
				
			|||||||
    'DbFactory',
 | 
					    'DbFactory',
 | 
				
			||||||
    'Db',
 | 
					    'Db',
 | 
				
			||||||
    'Transaction',
 | 
					    'Transaction',
 | 
				
			||||||
    'FallbackMapping',
 | 
					    'TransactionView',
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,19 +44,38 @@ class Request:
 | 
				
			|||||||
            await self.__future
 | 
					            await self.__future
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LineRequest(Request):
 | 
				
			||||||
 | 
					    __slots__ = (
 | 
				
			||||||
 | 
					        'line',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, line: str, /, *, future: Optional[asyncio.Future]) -> None:
 | 
				
			||||||
 | 
					        super().__init__(future)
 | 
				
			||||||
 | 
					        self.line = line
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class KVFactory:
 | 
					class KVFactory:
 | 
				
			||||||
    """note: unstable signature."""
 | 
					    """note: unstable signature."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    __slots__ = ()
 | 
					    __slots__ = ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def line(self, key: Any, value: Any, /) -> str:
 | 
					    def line(self, key: Any, value: Any, /) -> str:
 | 
				
			||||||
        """line must contain exactly one '\\n' at exactly the end if the line is not empty."""
 | 
					        """line must contain exactly one '\\n' at exactly the end if the line is not empty.
 | 
				
			||||||
 | 
					note: other forms of requests will later be represented by different methods or by instances of Action class."""
 | 
				
			||||||
        raise NotImplementedError
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def fromline(self, line: str, /) -> 'KVRequest':
 | 
					    def fromline(self, line: str, /) -> tuple[Any, Any]:
 | 
				
			||||||
        """inverse of line(). should use free() method to construct the request."""
 | 
					        """inverse of line(). should use free() method to construct the request.
 | 
				
			||||||
 | 
					note: unstable signature."""
 | 
				
			||||||
        raise NotImplementedError
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self, line: str, db: dict, /) -> None:
 | 
				
			||||||
 | 
					        """run request against the db.
 | 
				
			||||||
 | 
					extensible to allow forms of requests other than set.
 | 
				
			||||||
 | 
					note: unstable signature."""
 | 
				
			||||||
 | 
					        key, value = self.fromline(line)
 | 
				
			||||||
 | 
					        db[key] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def request(self, key: Any, value: Any, /, *, future: Optional[asyncio.Future]) -> 'KVRequest':
 | 
					    def request(self, key: Any, value: Any, /, *, future: Optional[asyncio.Future]) -> 'KVRequest':
 | 
				
			||||||
        """form request with Future.
 | 
					        """form request with Future.
 | 
				
			||||||
low-level API.
 | 
					low-level API.
 | 
				
			||||||
@ -69,7 +88,7 @@ note: unstable signature."""
 | 
				
			|||||||
        return self.request(key, value, future=None)
 | 
					        return self.request(key, value, future=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class KVRequest(Request):
 | 
					class KVRequest(LineRequest):
 | 
				
			||||||
    __slots__ = (
 | 
					    __slots__ = (
 | 
				
			||||||
        '__factory',
 | 
					        '__factory',
 | 
				
			||||||
        'key',
 | 
					        'key',
 | 
				
			||||||
@ -77,24 +96,18 @@ class KVRequest(Request):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, key: Any, value: Any, /, *, future: Optional[asyncio.Future], factory: KVFactory):
 | 
					    def __init__(self, key: Any, value: Any, /, *, future: Optional[asyncio.Future], factory: KVFactory):
 | 
				
			||||||
        super().__init__(future)
 | 
					        super().__init__(factory.line(key, value), future=future)
 | 
				
			||||||
        self.__factory = factory
 | 
					        self.__factory = factory
 | 
				
			||||||
        self.key = key
 | 
					        self.key = key
 | 
				
			||||||
        self.value = value
 | 
					        self.value = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def free(self, /):
 | 
					 | 
				
			||||||
        return self.__factory.free(self.key, self.value)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def line(self, /) -> str:
 | 
					 | 
				
			||||||
        return self.__factory.line(self.key, self.value)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DumpRequest(Request):
 | 
					class DumpRequest(Request):
 | 
				
			||||||
    __slots__ = ()
 | 
					    __slots__ = ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UnknownRequestType(TypeError):
 | 
					class UnknownRequestType(TypeError):
 | 
				
			||||||
    pass
 | 
					    __slots__ = ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class KVJson(KVFactory):
 | 
					class KVJson(KVFactory):
 | 
				
			||||||
@ -118,23 +131,20 @@ class KVJson(KVFactory):
 | 
				
			|||||||
                'unknown KVJson key type, cannot convert to hashable'
 | 
					                'unknown KVJson key type, cannot convert to hashable'
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def fromline(self, line: str, /) -> 'KVRequest':
 | 
					    def fromline(self, line: str, /) -> tuple[Any, Any]:
 | 
				
			||||||
        d = json.loads(line)
 | 
					        d = json.loads(line)
 | 
				
			||||||
        return self.free(self._load_key(d['key']), d['value'])
 | 
					        return self._load_key(d['key']), d['value']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TransactionRequest(Request):
 | 
					class TransactionRequest(LineRequest):
 | 
				
			||||||
    __slots__ = (
 | 
					    __slots__ = (
 | 
				
			||||||
        'buffer',
 | 
					        'buffer',
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, buffer: StringIO, /, *, future: Optional[asyncio.Future]):
 | 
					    def __init__(self, buffer: StringIO, /, *, future: Optional[asyncio.Future]):
 | 
				
			||||||
        super().__init__(future)
 | 
					        super().__init__(buffer.getvalue(), future=future)
 | 
				
			||||||
        self.buffer = buffer
 | 
					        self.buffer = buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def line(self, /) -> str:
 | 
					 | 
				
			||||||
        return self.buffer.getvalue()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DbConnection:
 | 
					class DbConnection:
 | 
				
			||||||
    """note: unstable constructor signature."""
 | 
					    """note: unstable constructor signature."""
 | 
				
			||||||
@ -208,8 +218,7 @@ note: unstable signature."""
 | 
				
			|||||||
        size = 0
 | 
					        size = 0
 | 
				
			||||||
        for line in io:
 | 
					        for line in io:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                request = self.__factory.kvfactory.fromline(line)
 | 
					                self.__factory.kvfactory.run(line, db)
 | 
				
			||||||
                db[request.key] = request.value
 | 
					 | 
				
			||||||
                size += len(line)
 | 
					                size += len(line)
 | 
				
			||||||
            except (json.JSONDecodeError, EOFError):
 | 
					            except (json.JSONDecodeError, EOFError):
 | 
				
			||||||
                traceback.print_exc()
 | 
					                traceback.print_exc()
 | 
				
			||||||
@ -222,7 +231,7 @@ note: unstable signature."""
 | 
				
			|||||||
note: unstable signature."""
 | 
					note: unstable signature."""
 | 
				
			||||||
        size = 0
 | 
					        size = 0
 | 
				
			||||||
        for key, value in db.items():
 | 
					        for key, value in db.items():
 | 
				
			||||||
            size += io.write(self.__factory.kvfactory.free(key, value).line())
 | 
					            size += io.write(self.__factory.kvfactory.line(key, value))
 | 
				
			||||||
        return size
 | 
					        return size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _path2db_sync(self, path: pathlib.Path, db: dict, /) -> int:
 | 
					    def _path2db_sync(self, path: pathlib.Path, db: dict, /) -> int:
 | 
				
			||||||
@ -240,10 +249,10 @@ note: unstable signature."""
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    async def set(self, key: Any, value: Any, /) -> None:
 | 
					    async def set(self, key: Any, value: Any, /) -> None:
 | 
				
			||||||
        """set the value and wait until it's written to disk."""
 | 
					        """set the value and wait until it's written to disk."""
 | 
				
			||||||
        self.__mmdb[key] = value
 | 
					 | 
				
			||||||
        future = self._create_future()
 | 
					        future = self._create_future()
 | 
				
			||||||
        self.__queue.put_nowait(
 | 
					        request = self.__factory.kvfactory.request(key, value, future=future)
 | 
				
			||||||
            self.__factory.kvfactory.request(key, value, future=future))
 | 
					        self.__mmdb[key] = value
 | 
				
			||||||
 | 
					        self.__queue.put_nowait(request)
 | 
				
			||||||
        await future
 | 
					        await future
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_nowait(self, key: Any, value: Any, /) -> None:
 | 
					    def set_nowait(self, key: Any, value: Any, /) -> None:
 | 
				
			||||||
@ -352,13 +361,11 @@ note: unstable signature."""
 | 
				
			|||||||
            await self._reload()
 | 
					            await self._reload()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def _handle_request(self, request: Request, /) -> None:
 | 
					    async def _handle_request(self, request: Request, /) -> None:
 | 
				
			||||||
        if isinstance(request, KVRequest):
 | 
					        if isinstance(request, LineRequest):
 | 
				
			||||||
            await self._write(request.line(), request)
 | 
					            await self._write(request.line, request)
 | 
				
			||||||
        elif isinstance(request, DumpRequest):
 | 
					        elif isinstance(request, DumpRequest):
 | 
				
			||||||
            await self._dump_buffer()
 | 
					            await self._dump_buffer()
 | 
				
			||||||
            request.set_result(None)
 | 
					            request.set_result(None)
 | 
				
			||||||
        elif isinstance(request, TransactionRequest):
 | 
					 | 
				
			||||||
            await self._write(request.line(), request)
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise UnknownRequestType
 | 
					            raise UnknownRequestType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -513,10 +520,11 @@ note: unstable signature."""
 | 
				
			|||||||
        future = self._create_future()
 | 
					        future = self._create_future()
 | 
				
			||||||
        self.__queue.put_nowait(TransactionRequest(buffer, future=future))
 | 
					        self.__queue.put_nowait(TransactionRequest(buffer, future=future))
 | 
				
			||||||
        await future
 | 
					        await future
 | 
				
			||||||
    
 | 
					
 | 
				
			||||||
    def submit_transaction(self, delta: dict, /) -> asyncio.Future | None:
 | 
					    def submit_transaction(self, delta: dict, future: asyncio.Future | None, /) -> None:
 | 
				
			||||||
        """not implemented.
 | 
					        """not implemented.
 | 
				
			||||||
low-level API."""
 | 
					low-level API.
 | 
				
			||||||
 | 
					note: unstable signature."""
 | 
				
			||||||
        raise NotImplementedError
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def commit(self, /) -> None:
 | 
					    async def commit(self, /) -> None:
 | 
				
			||||||
@ -575,7 +583,7 @@ class Db(DbConnection):
 | 
				
			|||||||
        await self.aclose()
 | 
					        await self.aclose()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FallbackMapping:
 | 
					class TransactionView:
 | 
				
			||||||
    """note: unstable constructor signature."""
 | 
					    """note: unstable constructor signature."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    __slots__ = (
 | 
					    __slots__ = (
 | 
				
			||||||
@ -620,7 +628,8 @@ bulk analog of DbConnection.set_nowait method."""
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Transaction:
 | 
					class Transaction:
 | 
				
			||||||
    """note: unstable signature."""
 | 
					    """note: unstable signature.
 | 
				
			||||||
 | 
					note: synchronous with is not implemented."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    __slots__ = (
 | 
					    __slots__ = (
 | 
				
			||||||
        '__connection',
 | 
					        '__connection',
 | 
				
			||||||
@ -632,11 +641,20 @@ class Transaction:
 | 
				
			|||||||
    def __init__(self, connection: DbConnection, /) -> None:
 | 
					    def __init__(self, connection: DbConnection, /) -> None:
 | 
				
			||||||
        self.__connection = connection
 | 
					        self.__connection = connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def __aenter__(self) -> FallbackMapping:
 | 
					    async def __aenter__(self) -> TransactionView:
 | 
				
			||||||
        self.__delta = {}
 | 
					        self.__delta = {}
 | 
				
			||||||
        return FallbackMapping(self.__delta, self.__connection)
 | 
					        return TransactionView(self.__delta, self.__connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def __aexit__(self, exc_type, exc_val, exc_tb):
 | 
					    async def __aexit__(self, exc_type, exc_val, exc_tb):
 | 
				
			||||||
        if exc_type is None:
 | 
					        if exc_type is None:
 | 
				
			||||||
            await self.__connection.complete_transaction(self.__delta)
 | 
					            await self.__connection.complete_transaction(self.__delta)
 | 
				
			||||||
        del self.__delta
 | 
					        del self.__delta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __enter__(self) -> TransactionView:
 | 
				
			||||||
 | 
					        self.__delta = {}
 | 
				
			||||||
 | 
					        return TransactionView(self.__delta, self.__connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __exit__(self, exc_type, exc_val, exc_tb):
 | 
				
			||||||
 | 
					        if exc_type is None:
 | 
				
			||||||
 | 
					            self.__connection.submit_transaction(self.__delta, None)
 | 
				
			||||||
 | 
					        del self.__delta
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							@ -2,7 +2,7 @@ from setuptools import setup
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
setup(
 | 
					setup(
 | 
				
			||||||
    name='ptvp35',
 | 
					    name='ptvp35',
 | 
				
			||||||
    version='1.0rc1',
 | 
					    version='1.0rc3',
 | 
				
			||||||
    packages=['ptvp35'],
 | 
					    packages=['ptvp35'],
 | 
				
			||||||
    url='https://gitea.ongoteam.net/PTV/ptvp35',
 | 
					    url='https://gitea.ongoteam.net/PTV/ptvp35',
 | 
				
			||||||
    license='',
 | 
					    license='',
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user