From 9f778a96100daa5af4ae8dcf978ecc5bb83ea990 Mon Sep 17 00:00:00 2001 From: bisho Date: Tue, 5 Mar 2024 15:26:58 +0100 Subject: [PATCH] Implement response heading parsing in rust (#54) ## Motivation / Description There was nothing else to optimize in python, but Rust can parse the string and build a ResponseFlags object in 22-50ns depending on the number of flags. While the interface between rust and python will never reach ns performance, it helps indeed finding the header in the response buffer. Initial (before all optimizations): multithreaded: Overall: 110779.55 RPS / 9.03 us/req singlethreaded: Overall: 111545.63 RPS / 8.96 us/req Python optimized: multithreaded: Overall: 193340.40 RPS / 5.17 us/req singlethreaded: Overall: 193036.56 RPS / 5.18 us/req Using rust for header parsing: multithreaded: Overall: 245898.34 RPS / 4.07 us/req singlethreaded: Overall: 246165.19 RPS / 4.06 us/req --- noxfile.py | 4 +- poetry.lock | 154 ++++++++++------ pyproject.toml | 1 + src/meta_memcache/__init__.py | 1 + .../commands/high_level_commands.py | 16 +- .../connection/memcache_socket.py | 65 ++++--- src/meta_memcache/executors/default.py | 5 +- .../extras/migrating_cache_client.py | 6 +- .../extras/probabilistic_hot_cache.py | 7 +- src/meta_memcache/protocol.py | 165 ++---------------- tests/commands_test.py | 135 +++++++++----- tests/memcache_socket_test.py | 16 +- tests/migrating_cache_client_test.py | 17 +- tests/probabilistic_hot_cache_test.py | 20 ++- 14 files changed, 301 insertions(+), 311 deletions(-) diff --git a/noxfile.py b/noxfile.py index 2af3053..342ef68 100644 --- a/noxfile.py +++ b/noxfile.py @@ -6,7 +6,7 @@ nox.options.sessions = "lint", "types", "tests" locations = "src", "tests", "noxfile.py" DEFAULT_VERSION = "3.8" -DEFAULT_BENCHMARK_VERSION = "3.11" +DEFAULT_BENCHMARK_VERSIONS = ["3.11"] VERSIONS = ["3.8", "3.11"] @@ -57,7 +57,7 @@ def tests(session: Session) -> None: session.run("pytest", *args, env={"PYTHONHASHSEED": "0"}) -@session(python=DEFAULT_BENCHMARK_VERSION) +@session(python=DEFAULT_BENCHMARK_VERSIONS) def benchmark(session: Session) -> None: """Run the benchmark suite.""" args = session.posargs diff --git a/poetry.lock b/poetry.lock index b0467ac..f4d4ceb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,56 +1,12 @@ +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. + [[package]] name = "marisa-trie" version = "1.0.0" description = "Static memory-efficient and fast Trie-like structures for Python." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -setuptools = "*" - -[package.extras] -test = ["hypothesis", "pytest", "readme-renderer"] - -[[package]] -name = "prometheus-client" -version = "0.17.1" -description = "Python client for the Prometheus monitoring system." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -twisted = ["twisted"] - -[[package]] -name = "setuptools" -version = "68.2.2" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "uhashring" -version = "2.3" -description = "Full featured consistent hashing python library compatible with ketama." -category = "main" optional = false python-versions = ">=3.7" - -[metadata] -lock-version = "1.1" -python-versions = "^3.8" -content-hash = "5ae5733989516d8828c4128c938074520f1edc772e205466fb5ae96bb0eb2864" - -[metadata.files] -marisa-trie = [ +files = [ {file = "marisa-trie-1.0.0.tar.gz", hash = "sha256:d8a68301f023a724eb379aaa1b10f88a9e1a458cc8a41b527c62507859b4a4d2"}, {file = "marisa_trie-1.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ac817d167f84bdc4de2a737fb5ffb635ba1a3fdf3dbbcc06541b2cb420416e79"}, {file = "marisa_trie-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6bc75721f4fb8c1d1d0812d2ed17e57ff77e7192c792114af941c12dab1a4346"}, @@ -112,15 +68,113 @@ marisa-trie = [ {file = "marisa_trie-1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34ec0f3bb54479a49dd60bb712fee851e7c135504d84efcf030815a07a9a96f5"}, {file = "marisa_trie-1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:eba700d6f24906dff4e593be1af93ebf38c841b0755ee701c816674f133dee29"}, ] -prometheus-client = [ + +[package.dependencies] +setuptools = "*" + +[package.extras] +test = ["hypothesis", "pytest", "readme-renderer"] + +[[package]] +name = "meta-memcache-socket" +version = "0.1.0" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "meta_memcache_socket-0.1.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:276ff6496b1bd2697c01bc7532bf212fd7bed53bca7f9860b097fefd44f0f255"}, + {file = "meta_memcache_socket-0.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85c877cdd9568377111ac8e45c3b1d3ac88fd3c7d1dbce769bc598e2917a8d13"}, + {file = "meta_memcache_socket-0.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50939f7225c0a1849fab1ba08441f5dfaea66e08520fbc79725df5da75ea11f9"}, + {file = "meta_memcache_socket-0.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13783808b431c149e0f4de7bdd631df6004a971d1717189b93796b63835fe108"}, + {file = "meta_memcache_socket-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8a8c2e62c7afdb4604a84638b17074266e5f285ca0fc9524af1ab81e13ea520"}, + {file = "meta_memcache_socket-0.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:77031dfd683856d9f3e5461d3b5234b6df008d45dbc6a85d0b9a94b55db2d7c8"}, + {file = "meta_memcache_socket-0.1.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:9a771144b6db23570fdf0f1008aa6017e65464e720e05038c8479a8146e2be19"}, + {file = "meta_memcache_socket-0.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b262cb9aa55424e840385c43af84b22bb16d0f29e8e15ce1ddf6afcf567b5bc"}, + {file = "meta_memcache_socket-0.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7429efaace51c28adc876f16994930d9c1a0507d1a3c4b2e0ee0a7070f22eba8"}, + {file = "meta_memcache_socket-0.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:976cd618023d78169b41b9cd9ae3bccccd185205241b5146a3e8859edb458f27"}, + {file = "meta_memcache_socket-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc3c87c581ff04902bce2a08c67e646142f33da4d0a2460d74ab0c2701a7cc09"}, + {file = "meta_memcache_socket-0.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:98efeca606829f4f5bd7add7b1c698a3a6da2565df2d239295d162484f334e04"}, + {file = "meta_memcache_socket-0.1.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:fe050b98fcdef5c8b1c0c993f69397220b0a605884c2439be6fd4cb8fd4274f0"}, + {file = "meta_memcache_socket-0.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b2b93aa16620c224735d162e2cb9b5623a3c46c43a7a33f4c1c6fcd1df1d55e"}, + {file = "meta_memcache_socket-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77bb692a2522c70ca57bd393eb664148957982a607b1bc07cc18d4c733c504d5"}, + {file = "meta_memcache_socket-0.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:55f92f2bd27a3bb96fb0d1e0ab34196ff3929f84656852a5ddaac578fbf7c1c8"}, + {file = "meta_memcache_socket-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682007ae78f1eaeaa3eaabb4db956d4b6950d34737dd4e0cd99c1b0efb477b14"}, + {file = "meta_memcache_socket-0.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39e7b14f54c634861a13befb5a753991f8535378237c0680f0445cbbf1a72509"}, + {file = "meta_memcache_socket-0.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1c25df35051ecdae27a6052d4d533f3e8290bf49ca7f51f6960691d990b9800"}, + {file = "meta_memcache_socket-0.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:815fd108ab5b3dfcf974315c88d799eff26801203d50d1cc31a1d387610626ce"}, + {file = "meta_memcache_socket-0.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f22f9dfcee14cfcc508d235e4862721a88d8167e75b51c1679876a807b2fa3cc"}, + {file = "meta_memcache_socket-0.1.0-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:89a197c4f9b62baf0ceede51e678d86a939083500f8d648315626f11d9fc19f4"}, + {file = "meta_memcache_socket-0.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e7e35b06f06292db5edcb542e14a3b875c215839e49dc3dd061dadcba6285d5"}, + {file = "meta_memcache_socket-0.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5741db35da60f514321430a24cafb8a45fd3c6253e19f193175446d1c71a66f8"}, + {file = "meta_memcache_socket-0.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3150319f6873ec8a65d523fc8da9fe2ee99154a2bc73552a8446510e556a500"}, + {file = "meta_memcache_socket-0.1.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bef08e5527df2e287e89bc04dda9fce554ddb7e782b497ab97b735ebca653792"}, + {file = "meta_memcache_socket-0.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0bd85fca14237950f850a7bfaaa9cdc09f7e63bc1f453eed58c5921cdfaaec46"}, + {file = "meta_memcache_socket-0.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f47eaa393d0cc5c4c2b4b87ee433698cda353e1d60107315891293bf7ef4eff0"}, + {file = "meta_memcache_socket-0.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:876767acbc5ae7ff7494bcbb2488b260928e0016bc73906bc94ce5be047fe974"}, + {file = "meta_memcache_socket-0.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbc18c52f844a0eea17037718931c44f79597d2a594c9cff58c0664e76df85b"}, + {file = "meta_memcache_socket-0.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50ba38faabaefe49b39c57e2abca30ffb060b6aaefb928fae9a940fa74a69856"}, + {file = "meta_memcache_socket-0.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d07f0f8e18f155c9d97a667a084d026aa16db681be020a7dad8d3443da073225"}, + {file = "meta_memcache_socket-0.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4652f1455e0478ee825ca6828b368f60f9acd582c2d94298e624cf2804bbaac3"}, + {file = "meta_memcache_socket-0.1.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91b8aad8bfcb327f440cc6fbdc893aac20a36638b88fdd494ab1e36776455977"}, + {file = "meta_memcache_socket-0.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0b338a9b0b07823d1653ab4a2f6d1d7db5e70416968e64cc070fd983d7e47c4"}, + {file = "meta_memcache_socket-0.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4de8135df114d97e99fa4171a10504812aaab2405e1ffb9987c4aa26ee11569b"}, + {file = "meta_memcache_socket-0.1.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f9baa601dba6cc9f6838472c9f8acbb4226a8279886e237c2f1a02ca752d558"}, + {file = "meta_memcache_socket-0.1.0-pp37-pypy37_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fdf92bf7eec9b340c49f8059a3032cd5cf70dbf42eca1f5db65d5e51dc6ad17"}, + {file = "meta_memcache_socket-0.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4026106469d5fc707c5920b7ef98c5faad0fd8c9c2411c9cf169e629d433f7a5"}, + {file = "meta_memcache_socket-0.1.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5e56237cb45343135d4e78aaed1a6045d8f7a95beaba2a836a423c0dfa6a5799"}, + {file = "meta_memcache_socket-0.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d6e97e1ac7133bf3514c122aebd443754c7ac8138c27b48a4470e260b9092f4"}, + {file = "meta_memcache_socket-0.1.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e8766dbe2ebd9b9abe536eb62648e8edbff5c9e79bb60f7bd42bc8f09dca8825"}, + {file = "meta_memcache_socket-0.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d835e0b69c357ae83d17fd078efc0e464a62397ae732ca58e169381284311bfb"}, + {file = "meta_memcache_socket-0.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c10ccb6f62c90ab1bb6345ecf504e1d5d8450cd7a81be8d15322c1eff0c0e0e7"}, + {file = "meta_memcache_socket-0.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8164586cbc9ac16376945f14c261b49b8905f2d5b3ef2207cffe90a1f0ca86db"}, + {file = "meta_memcache_socket-0.1.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd661f503ecc9ae8020bca79fcfb814f710df2e91cf98086636d5ae9e7689ef0"}, + {file = "meta_memcache_socket-0.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ab910371da3d13abd366d1fb36e0c961b5fbad85c257265bb18603dbd52d0d3"}, + {file = "meta_memcache_socket-0.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b69fbb37d392d1cbae85deabc4efd475eea5867b6b45469add3300e21115880"}, + {file = "meta_memcache_socket-0.1.0.tar.gz", hash = "sha256:244a53055c069dd232dd8733c93a494ce4e437a5bfa2fb14ef7ec8f51c11d76d"}, +] + +[[package]] +name = "prometheus-client" +version = "0.17.1" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.6" +files = [ {file = "prometheus_client-0.17.1-py3-none-any.whl", hash = "sha256:e537f37160f6807b8202a6fc4764cdd19bac5480ddd3e0d463c3002b34462101"}, {file = "prometheus_client-0.17.1.tar.gz", hash = "sha256:21e674f39831ae3f8acde238afd9a27a37d0d2fb5a28ea094f0ce25d2cbf2091"}, ] -setuptools = [ + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] -uhashring = [ + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "uhashring" +version = "2.3" +description = "Full featured consistent hashing python library compatible with ketama." +optional = false +python-versions = ">=3.7" +files = [ {file = "uhashring-2.3-py3-none-any.whl", hash = "sha256:7ee8a25ca495a97effad10bd563c83b4054a6d7606d9530757049a04edab9297"}, {file = "uhashring-2.3.tar.gz", hash = "sha256:9f76187e8d8e82f6e5519c995eef1f1bf44d4a5e0fc4fdd1219a044b10040612"}, ] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "ce30e2f682597672f0fc2ef59821c0be70841bfd087134b66b86c1f7580d7d25" diff --git a/pyproject.toml b/pyproject.toml index 13d0f1e..84c918c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ packages = [{include = "meta_memcache", from="src"}] python = "^3.8" uhashring = "^2.1" marisa-trie = "^1.0.0" +meta-memcache-socket = "^0.1.0" [tool.poetry.group.extras.dependencies] prometheus-client = "^0.17.1" diff --git a/src/meta_memcache/__init__.py b/src/meta_memcache/__init__.py index d0f4391..6dee464 100644 --- a/src/meta_memcache/__init__.py +++ b/src/meta_memcache/__init__.py @@ -40,6 +40,7 @@ Miss, NotStored, ServerVersion, + ResponseFlags, SetMode, Success, TokenFlag, diff --git a/src/meta_memcache/commands/high_level_commands.py b/src/meta_memcache/commands/high_level_commands.py index 582b6be..356bf1d 100644 --- a/src/meta_memcache/commands/high_level_commands.py +++ b/src/meta_memcache/commands/high_level_commands.py @@ -287,23 +287,23 @@ def get_or_lease_cas( if isinstance(result, Value): # It is a hit. - if result.win: + if result.flags.win: # Win flag present, meaning we got the lease to # recache/cache the item. We need to mimic a miss. - return None, result.cas_token - if result.size == 0 and result.win is False: + return None, result.flags.cas_token + if result.size == 0 and result.flags.win is False: # The value is empty, this is a miss lease, # and we lost, so we must keep retrying and - # wait for the winner to populate the value. + # wait for the.flags.winner to populate the value. if i < lease_policy.miss_retries: continue else: # We run out of retries, behave as a miss - return None, result.cas_token + return None, result.flags.cas_token else: # There is data, either the is no lease or # we lost and should use the stale value. - return result.value, result.cas_token + return result.value, result.flags.cas_token else: # With MISS_LEASE_TTL we should always get a value # because on miss a lease empty value is generated @@ -378,7 +378,7 @@ def get_cas( if result is None: return None, None else: - return result.value, result.cas_token + return result.value, result.flags.cas_token def _get( self: HighLevelCommandMixinWithMetaCommands, @@ -414,7 +414,7 @@ def _process_get_result( ) -> Optional[Value]: if isinstance(result, Value): # It is a hit - if result.win: + if result.flags.win: # Win flag present, meaning we got the lease to # recache the item. We need to mimic a miss, so # we set the value to None. diff --git a/src/meta_memcache/connection/memcache_socket.py b/src/meta_memcache/connection/memcache_socket.py index 4c1d967..5240347 100644 --- a/src/meta_memcache/connection/memcache_socket.py +++ b/src/meta_memcache/connection/memcache_socket.py @@ -1,17 +1,21 @@ import logging import socket -from typing import Union +from typing import Optional, Tuple, Union + +import meta_memcache_socket from meta_memcache.errors import MemcacheError from meta_memcache.protocol import ( ENDL, ENDL_LEN, + EMPTY_RESPONSE_FLAGS, NOOP, Conflict, Miss, NotStored, ServerVersion, Success, + ResponseFlags, Value, get_store_success_response_header, ) @@ -119,7 +123,9 @@ def _reset_buffer(self) -> None: self._pos = 0 self._read = remaining_data - def _get_single_header(self) -> memoryview: + def _get_single_header( + self, + ) -> Tuple[int, Optional[int], Optional[int], Optional[ResponseFlags]]: # Reset buffer for new data if self._read == self._pos: self._read = 0 @@ -127,22 +133,19 @@ def _get_single_header(self) -> memoryview: elif self._pos > self._reset_buffer_size: self._reset_buffer() - endl_pos = -1 while True: - if self._read - self._pos > ENDL_LEN: - endl_pos = self._buf.find(ENDL, self._pos, self._read) - if endl_pos >= 0: - break + if self._read != self._pos: + # We have data in the buffer: find the header + if header_data := meta_memcache_socket.parse_header( + self._buf_view, self._pos, self._read + ): + self._pos = header_data[0] + return header_data # Missing data, but still space in buffer, so read more if self._recv_info_buffer() <= 0: break - if endl_pos < 0: - raise MemcacheError("Bad response. Socket might have closed unexpectedly") - - pos = self._pos - self._pos = endl_pos + ENDL_LEN - return self._buf_view[pos:endl_pos] + raise MemcacheError("Bad response. Socket might have closed unexpectedly") def sendall(self, data: bytes, with_noop: bool = False) -> None: if with_noop: @@ -153,10 +156,12 @@ def sendall(self, data: bytes, with_noop: bool = False) -> None: def _read_until_noop_header(self) -> None: while self._noop_expected > 0: header = self._get_single_header() - if header[0:2] == b"MN": + if header[1] == meta_memcache_socket.RESPONSE_NOOP: self._noop_expected -= 1 - def _get_header(self) -> memoryview: + def _get_header( + self, + ) -> Tuple[int, Optional[int], Optional[int], Optional[ResponseFlags]]: try: if self._noop_expected > 0: self._read_until_noop_header() @@ -169,30 +174,36 @@ def _get_header(self) -> memoryview: def get_response( self, ) -> Union[Value, Success, NotStored, Conflict, Miss]: - header = self._get_header().tobytes() - response_code = header[0:2] + (_, response_code, size, flags) = self._get_header() result: Union[Value, Success, NotStored, Conflict, Miss] try: - if response_code == b"VA": + if response_code == meta_memcache_socket.RESPONSE_VALUE: + if size is None: + raise MemcacheError("Bad value response. Missing size") # Value response - result = Value.from_header(header) - elif response_code == self._store_success_response_header: + result = Value( + size=size, flags=flags or EMPTY_RESPONSE_FLAGS, value=None + ) + elif response_code == meta_memcache_socket.RESPONSE_SUCCESS: # Stored or no value, return Success - result = Success.from_header(header) - elif response_code == b"NS": + result = Success(flags=flags or EMPTY_RESPONSE_FLAGS) + elif response_code == meta_memcache_socket.RESPONSE_NOT_STORED: # Value response, parse size and flags result = NOT_STORED - elif response_code == b"EX": + elif response_code == meta_memcache_socket.RESPONSE_CONFLICT: # Already exists, not changed, CAS conflict result = CONFLICT - elif response_code == b"EN" or response_code == b"NF": + elif response_code == meta_memcache_socket.RESPONSE_MISS: # Not Found, Miss. result = MISS else: - raise MemcacheError(f"Unknown response: {bytes(response_code)!r}") + raise MemcacheError(f"Unknown response: {response_code}") except Exception as e: - _log.warning(f"Error parsing response header in {self}: {header!r}") - raise MemcacheError(f"Error parsing response header {header!r}") from e + _log.warning( + f"Error parsing response header in {self}: " + f"Response: {response_code}, size {size}, flags: {flags}" + ) + raise MemcacheError("Error parsing response header") from e return result diff --git a/src/meta_memcache/executors/default.py b/src/meta_memcache/executors/default.py index 6e382ae..0383aa6 100644 --- a/src/meta_memcache/executors/default.py +++ b/src/meta_memcache/executors/default.py @@ -17,6 +17,7 @@ MetaCommand, Miss, NotStored, + ResponseFlags, ServerVersion, Success, TokenFlag, @@ -252,12 +253,12 @@ def _conn_recv_response( Read response on a connection """ if flags and Flag.NOREPLY in flags: - return Success() + return Success(flags=ResponseFlags()) result = conn.get_response() if isinstance(result, Value): data = conn.get_value(result.size) if result.size > 0: - encoding_id = result.client_flag or 0 + encoding_id = result.flags.client_flag or 0 try: result.value = self._serializer.unserialize(data, encoding_id) except Exception: diff --git a/src/meta_memcache/extras/migrating_cache_client.py b/src/meta_memcache/extras/migrating_cache_client.py index dac181f..664ffd4 100644 --- a/src/meta_memcache/extras/migrating_cache_client.py +++ b/src/meta_memcache/extras/migrating_cache_client.py @@ -78,7 +78,11 @@ def get_migration_mode(self) -> MigrationMode: return current_mode def _get_value_ttl(self, value: Value) -> int: - ttl = value.ttl if value.ttl is not None else self._default_read_backfill_ttl + ttl = ( + value.flags.ttl + if value.flags.ttl is not None + else self._default_read_backfill_ttl + ) if ttl < 0: # TTL for items marked to store forvered is returned as -1 ttl = 0 diff --git a/src/meta_memcache/extras/probabilistic_hot_cache.py b/src/meta_memcache/extras/probabilistic_hot_cache.py index 1e69699..3766a85 100644 --- a/src/meta_memcache/extras/probabilistic_hot_cache.py +++ b/src/meta_memcache/extras/probabilistic_hot_cache.py @@ -124,10 +124,11 @@ def _store_in_hot_cache_if_necessary( allowed: bool, ) -> None: if not is_hot: - hit_after_write = value.fetched or 0 - last_read_age = value.last_access if value.last_access is not None else 9999 + last_read_age = ( + value.flags.last_access if value.flags.last_access is not None else 9999 + ) if ( - hit_after_write > 0 + value.flags.fetched and last_read_age <= self._max_last_access_age_seconds ): # Is detected as hot diff --git a/src/meta_memcache/protocol.py b/src/meta_memcache/protocol.py index 95216d0..37302db 100644 --- a/src/meta_memcache/protocol.py +++ b/src/meta_memcache/protocol.py @@ -2,6 +2,8 @@ from enum import Enum, IntEnum from typing import Any, Dict, List, Optional, Union +from meta_memcache_socket import ResponseFlags + ENDL = b"\r\n" NOOP: bytes = b"mn" + ENDL ENDL_LEN = 2 @@ -103,168 +105,25 @@ class Miss(MemcacheResponse): # Response flags -TOKEN_FLAG_OPAQUE = ord("O") -INT_FLAG_CAS_TOKEN = ord("c") -INT_FLAG_FETCHED = ord("h") -INT_FLAG_LAST_ACCESS = ord("l") -INT_FLAG_TTL = ord("t") -INT_FLAG_CLIENT_FLAG = ord("f") -INT_FLAG_SIZE = ord("s") -FLAG_WIN = ord("W") -FLAG_LOST = ord("Z") -FLAG_STALE = ord("X") - - -# @dataclass(slots=True, init=False) +EMPTY_RESPONSE_FLAGS = ResponseFlags() + + @dataclass class Success(MemcacheResponse): - __slots__ = ( - "cas_token", - "fetched", - "last_access", - "ttl", - "client_flag", - "win", - "stale", - "real_size", - "opaque", - ) - cas_token: Optional[int] - fetched: Optional[int] - last_access: Optional[int] - ttl: Optional[int] - client_flag: Optional[int] - win: Optional[bool] - stale: bool - real_size: Optional[int] - opaque: Optional[bytes] - - def __init__( - self, - *, - cas_token: Optional[int] = None, - fetched: Optional[int] = None, - last_access: Optional[int] = None, - ttl: Optional[int] = None, - client_flag: Optional[int] = None, - win: Optional[bool] = None, - stale: bool = False, - real_size: Optional[int] = None, - opaque: Optional[bytes] = None, - ) -> None: - self.cas_token = cas_token - self.fetched = fetched - self.last_access = last_access - self.ttl = ttl - self.client_flag = client_flag - self.win = win - self.stale = stale - self.real_size = real_size - self.opaque = opaque + __slots__ = ("flags",) + flags: ResponseFlags @classmethod - def from_header(cls, header: "Blob") -> "Success": - result = cls() - result._set_flags(header) - return result - - def _set_flags(self, header: bytes, pos: int = 3) -> None: # noqa: C901 - header_size = len(header) - while pos < header_size: - flag = header[pos] - pos += 1 - if flag == SPACE: - continue - end = pos - while end < header_size: - if header[end] == SPACE: - break - end += 1 - - if flag == INT_FLAG_CAS_TOKEN: - self.cas_token = int(header[pos:end]) - elif flag == INT_FLAG_FETCHED: - self.fetched = int(header[pos:end]) - elif flag == INT_FLAG_LAST_ACCESS: - self.last_access = int(header[pos:end]) - elif flag == INT_FLAG_TTL: - self.ttl = int(header[pos:end]) - elif flag == INT_FLAG_CLIENT_FLAG: - self.client_flag = int(header[pos:end]) - elif flag == FLAG_WIN: - self.win = True - elif flag == FLAG_LOST: - self.win = False - elif flag == FLAG_STALE: - self.stale = True - elif flag == INT_FLAG_SIZE: - self.real_size = int(header[pos:end]) - elif flag == TOKEN_FLAG_OPAQUE: - self.opaque = header[pos:end] - pos = end + 1 - - -# @dataclass(slots=True, init=False) + def default(cls) -> "Success": + return cls(flags=ResponseFlags()) + + @dataclass class Value(Success): - __slots__ = ( - "cas_token", - "fetched", - "last_access", - "ttl", - "client_flag", - "win", - "stale", - "real_size", - "opaque", - "size", - "value", - ) + __slots__ = ("flags", "size", "value") size: int value: Optional[Any] - def __init__( - self, - *, - size: int, - value: Optional[Any] = None, - cas_token: Optional[int] = None, - fetched: Optional[int] = None, - last_access: Optional[int] = None, - ttl: Optional[int] = None, - client_flag: Optional[int] = None, - win: Optional[bool] = None, - stale: bool = False, - real_size: Optional[int] = None, - opaque: Optional[bytes] = None, - ) -> None: - self.size = size - self.value = value - self.cas_token = cas_token - self.fetched = fetched - self.last_access = last_access - self.ttl = ttl - self.client_flag = client_flag - self.win = win - self.stale = stale - self.real_size = real_size - self.opaque = opaque - - @classmethod - def from_header(cls, header: "Blob") -> "Value": - header_size = len(header) - if header_size < 4 or header[2] != SPACE: - raise ValueError(f"Invalid header {header!r}") - end = 4 - while end < header_size: - if header[end] == SPACE: - break - end += 1 - size = int(header[3:end]) - result = cls(size=size) - result._set_flags(header, pos=end + 1) - return result - @dataclass class ValueContainer: diff --git a/tests/commands_test.py b/tests/commands_test.py index c48d791..3bd2241 100644 --- a/tests/commands_test.py +++ b/tests/commands_test.py @@ -27,6 +27,7 @@ from meta_memcache.protocol import ( Miss, NotStored, + ResponseFlags, ServerVersion, Success, IntFlag, @@ -133,7 +134,7 @@ def test_set_cmd( memcache_socket: MemcacheSocket, cache_client: CacheClient, ) -> None: - memcache_socket.get_response.return_value = Success() + memcache_socket.get_response.return_value = Success(flags=ResponseFlags()) cache_client.set(key="foo", value="bar", ttl=300) memcache_socket.sendall.assert_called_once_with( @@ -277,7 +278,7 @@ def test_set_cmd_1_6_6( memcache_socket_1_6_6: MemcacheSocket, cache_client_1_6_6: CacheClient, ) -> None: - memcache_socket_1_6_6.get_response.return_value = Success() + memcache_socket_1_6_6.get_response.return_value = Success(flags=ResponseFlags()) cache_client_1_6_6.set(key="foo", value="bar", ttl=300) memcache_socket_1_6_6.sendall.assert_called_once_with( @@ -290,7 +291,7 @@ def test_set_success_fail( memcache_socket: MemcacheSocket, cache_client: CacheClient, ) -> None: - memcache_socket.get_response.return_value = Success() + memcache_socket.get_response.return_value = Success(flags=ResponseFlags()) result = cache_client.set(key=Key("foo"), value="bar", ttl=300) assert result is True @@ -302,7 +303,7 @@ def test_set_success_fail( def test_refill( cache_client_with_mocked_meta_commands: CacheApi, meta_command_mock: MagicMock ) -> None: - meta_command_mock.meta_set.return_value = Success() + meta_command_mock.meta_set.return_value = Success(flags=ResponseFlags()) cache_client_with_mocked_meta_commands.refill(key="foo", value="bar", ttl=300) meta_command_mock.meta_set.assert_called_once_with( key=Key(key="foo"), @@ -319,7 +320,7 @@ def test_delete_cmd( memcache_socket: MemcacheSocket, cache_client: CacheClient, ) -> None: - memcache_socket.get_response.return_value = Success() + memcache_socket.get_response.return_value = Success(flags=ResponseFlags()) cache_client.delete(key="foo") memcache_socket.sendall.assert_called_once_with(b"md foo\r\n", with_noop=False) @@ -367,7 +368,7 @@ def test_delete_success_fail( memcache_socket: MemcacheSocket, cache_client: CacheClient, ) -> None: - memcache_socket.get_response.return_value = Success() + memcache_socket.get_response.return_value = Success(flags=ResponseFlags()) result = cache_client.delete(key=Key("foo")) assert result is True @@ -384,7 +385,7 @@ def test_invalidate_cmd( memcache_socket: MemcacheSocket, cache_client: CacheClient, ) -> None: - memcache_socket.get_response.return_value = Success() + memcache_socket.get_response.return_value = Success(flags=ResponseFlags()) cache_client.invalidate(key="foo") memcache_socket.sendall.assert_called_once_with(b"md foo\r\n", with_noop=False) @@ -432,7 +433,7 @@ def test_invalidate_success_fail( memcache_socket: MemcacheSocket, cache_client: CacheClient, ) -> None: - memcache_socket.get_response.return_value = Success() + memcache_socket.get_response.return_value = Success(flags=ResponseFlags()) result = cache_client.invalidate(key=Key("foo")) assert result is True @@ -449,7 +450,7 @@ def test_touch_cmd( memcache_socket: MemcacheSocket, cache_client: CacheClient, ) -> None: - memcache_socket.get_response.return_value = Success() + memcache_socket.get_response.return_value = Success(flags=ResponseFlags()) cache_client.touch(key="foo", ttl=60) memcache_socket.sendall.assert_called_once_with(b"mg foo T60\r\n", with_noop=False) @@ -564,7 +565,9 @@ def test_get_cmd(memcache_socket: MemcacheSocket, cache_client: CacheClient) -> ) memcache_socket.sendall.reset_mock() - memcache_socket.get_response.return_value = Value(size=0, value=None) + memcache_socket.get_response.return_value = Value( + size=0, value=None, flags=ResponseFlags() + ) cache_client.get_or_lease( key=Key("foo"), lease_policy=LeasePolicy(), @@ -618,8 +621,10 @@ def test_get_value(memcache_socket: MemcacheSocket, cache_client: CacheClient) - memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, - cas_token=expected_cas_token, - client_flag=encoded_value.encoding_id, + flags=ResponseFlags( + cas_token=expected_cas_token, + client_flag=encoded_value.encoding_id, + ), ) memcache_socket.get_value.return_value = encoded_value.data @@ -642,7 +647,7 @@ def test_get_value(memcache_socket: MemcacheSocket, cache_client: CacheClient) - def test_get_other(memcache_socket: MemcacheSocket, cache_client: CacheClient) -> None: - memcache_socket.get_response.return_value = Success() + memcache_socket.get_response.return_value = Success(flags=ResponseFlags()) try: cache_client.get( key=Key("foo"), @@ -661,8 +666,10 @@ def test_value_wrong_type( memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, - cas_token=expected_cas_token, - client_flag=encoded_value.encoding_id, + flags=ResponseFlags( + cas_token=expected_cas_token, + client_flag=encoded_value.encoding_id, + ), ) memcache_socket.get_value.return_value = encoded_value.data @@ -695,8 +702,10 @@ def test_deserialization_error( memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, - cas_token=expected_cas_token, - client_flag=encoded_value.encoding_id, + flags=ResponseFlags( + cas_token=expected_cas_token, + client_flag=encoded_value.encoding_id, + ), ) memcache_socket.get_value.return_value = encoded_value.data @@ -720,10 +729,12 @@ def test_recache_win_returns_miss( memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, - win=True, - stale=True, - cas_token=expected_cas_token, - client_flag=encoded_value.encoding_id, + flags=ResponseFlags( + win=True, + stale=True, + cas_token=expected_cas_token, + client_flag=encoded_value.encoding_id, + ), ) memcache_socket.get_value.return_value = encoded_value.data @@ -741,10 +752,12 @@ def test_recache_lost_returns_stale_value( memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, - win=False, - stale=True, - cas_token=expected_cas_token, - client_flag=encoded_value.encoding_id, + flags=ResponseFlags( + win=False, + stale=True, + cas_token=expected_cas_token, + client_flag=encoded_value.encoding_id, + ), ) memcache_socket.get_value.return_value = encoded_value.data @@ -762,8 +775,10 @@ def test_get_or_lease_hit( memcache_socket.get_response.return_value = Value( size=len(encoded_value.data), value=None, - cas_token=expected_cas_token, - client_flag=encoded_value.encoding_id, + flags=ResponseFlags( + cas_token=expected_cas_token, + client_flag=encoded_value.encoding_id, + ), ) memcache_socket.get_value.return_value = encoded_value.data @@ -786,8 +801,10 @@ def test_get_or_lease_miss_win( memcache_socket.get_response.return_value = Value( size=0, value=None, - win=True, - cas_token=expected_cas_token, + flags=ResponseFlags( + win=True, + cas_token=expected_cas_token, + ), ) memcache_socket.get_value.return_value = b"" @@ -813,20 +830,26 @@ def test_get_or_lease_miss_lost_then_data( Value( size=0, value=None, - win=False, - cas_token=expected_cas_token - 1, + flags=ResponseFlags( + win=False, + cas_token=expected_cas_token - 1, + ), ), Value( size=0, value=None, - win=False, - cas_token=expected_cas_token - 1, + flags=ResponseFlags( + win=False, + cas_token=expected_cas_token - 1, + ), ), Value( size=len(encoded_value.data), value=None, - cas_token=expected_cas_token, - client_flag=encoded_value.encoding_id, + flags=ResponseFlags( + cas_token=expected_cas_token, + client_flag=encoded_value.encoding_id, + ), ), ] memcache_socket.get_value.side_effect = [b"", b"", encoded_value.data] @@ -863,20 +886,26 @@ def test_get_or_lease_miss_lost_then_win( Value( size=0, value=None, - win=False, - cas_token=expected_cas_token - 1, + flags=ResponseFlags( + win=False, + cas_token=expected_cas_token - 1, + ), ), Value( size=0, value=None, - win=False, - cas_token=expected_cas_token - 1, + flags=ResponseFlags( + win=False, + cas_token=expected_cas_token - 1, + ), ), Value( size=0, value=None, - win=True, - cas_token=expected_cas_token, + flags=ResponseFlags( + win=True, + cas_token=expected_cas_token, + ), ), ] memcache_socket.get_value.side_effect = [b"", b"", b""] @@ -912,8 +941,10 @@ def test_get_or_lease_miss_runs_out_of_retries( memcache_socket.get_response.return_value = Value( size=0, value=None, - win=False, - cas_token=expected_cas_token, + flags=ResponseFlags( + win=False, + cas_token=expected_cas_token, + ), ) memcache_socket.get_value.return_value = b"" @@ -1202,7 +1233,7 @@ def test_delta_cmd(memcache_socket: MemcacheSocket, cache_client: CacheClient) - memcache_socket.sendall.reset_mock() memcache_socket.get_response.reset_mock() - memcache_socket.get_response.return_value = Success() + memcache_socket.get_response.return_value = Success(flags=ResponseFlags()) result = cache_client.delta(key=Key("foo"), delta=1) assert result is True @@ -1210,7 +1241,9 @@ def test_delta_cmd(memcache_socket: MemcacheSocket, cache_client: CacheClient) - memcache_socket.sendall.reset_mock() memcache_socket.get_response.reset_mock() - memcache_socket.get_response.return_value = Value(size=2, value=None) + memcache_socket.get_response.return_value = Value( + size=2, value=None, flags=ResponseFlags() + ) memcache_socket.get_value.return_value = b"10" result = cache_client.delta_and_get(key=Key("foo"), delta=1) @@ -1234,8 +1267,16 @@ def test_delta_cmd(memcache_socket: MemcacheSocket, cache_client: CacheClient) - def test_multi_get(memcache_socket: MemcacheSocket, cache_client: CacheClient) -> None: memcache_socket.get_response.side_effect = [ Miss(), - Value(size=2, value=None, client_flag=MixedSerializer.BINARY), - Value(size=2, value=None, win=True), + Value( + size=2, + value=None, + flags=ResponseFlags(client_flag=MixedSerializer.BINARY), + ), + Value( + size=2, + value=None, + flags=ResponseFlags(win=True), + ), ] memcache_socket.get_value.return_value = b"OK" diff --git a/tests/memcache_socket_test.py b/tests/memcache_socket_test.py index 796b21b..455c17d 100644 --- a/tests/memcache_socket_test.py +++ b/tests/memcache_socket_test.py @@ -67,11 +67,11 @@ def test_get_response( ms = MemcacheSocket(fake_socket) result = ms.get_response() assert isinstance(result, Success) - assert result.cas_token == 1 + assert result.flags.cas_token == 1 result = ms.get_response() assert isinstance(result, Value) - assert result.cas_token == 1 + assert result.flags.cas_token == 1 assert result.size == 2 @@ -84,11 +84,11 @@ def test_get_response_1_6_6( ms = MemcacheSocket(fake_socket, version=ServerVersion.AWS_1_6_6) result = ms.get_response() assert isinstance(result, Success) - assert result.cas_token == 1 + assert result.flags.cas_token == 1 result = ms.get_response() assert isinstance(result, Value) - assert result.cas_token == 1 + assert result.flags.cas_token == 1 assert result.size == 2 @@ -121,7 +121,7 @@ def test_get_value( ms = MemcacheSocket(fake_socket) result = ms.get_response() assert isinstance(result, Value) - assert result.cas_token == 1 + assert result.flags.cas_token == 1 assert result.size == 2 ms.get_value(2) @@ -135,9 +135,9 @@ def test_get_value_large( ms = MemcacheSocket(fake_socket, buffer_size=100) result = ms.get_response() assert isinstance(result, Value) - assert result.cas_token == 1 - assert result.win is True - assert result.opaque == b"xxx" + assert result.flags.cas_token == 1 + assert result.flags.win is True + assert bytes(result.flags.opaque) == b"xxx" assert result.size == 200 value = ms.get_value(result.size) assert len(value) == result.size diff --git a/tests/migrating_cache_client_test.py b/tests/migrating_cache_client_test.py index bcb1749..b922291 100644 --- a/tests/migrating_cache_client_test.py +++ b/tests/migrating_cache_client_test.py @@ -2,7 +2,14 @@ from meta_memcache.interfaces.router import DEFAULT_FAILURE_HANDLING import pytest -from meta_memcache import CacheClient, IntFlag, Key, Value, WriteFailureEvent +from meta_memcache import ( + CacheClient, + IntFlag, + Key, + Value, + WriteFailureEvent, + ResponseFlags, +) from meta_memcache.extras.migrating_cache_client import ( MigratingCacheClient, MigrationMode, @@ -75,13 +82,17 @@ def _set_cache_client_mock_get_return_values(client: Mock, ttl: int = 10) -> Non client.meta_get.return_value = Value( size=3, value="bar", - ttl=ttl, + flags=ResponseFlags( + ttl=ttl, + ), ) client.meta_multiget.return_value = { Key(key="foo", routing_key=None, is_unicode=False): Value( size=3, value="bar", - ttl=ttl, + flags=ResponseFlags( + ttl=ttl, + ), ) } diff --git a/tests/probabilistic_hot_cache_test.py b/tests/probabilistic_hot_cache_test.py index 728bdc5..4e02fcf 100644 --- a/tests/probabilistic_hot_cache_test.py +++ b/tests/probabilistic_hot_cache_test.py @@ -13,7 +13,7 @@ ProbabilisticHotCache, ) from meta_memcache.metrics.prometheus import PrometheusMetricsCollector -from meta_memcache.protocol import Flag, Miss, ReadResponse, TokenFlag +from meta_memcache.protocol import Flag, Miss, ReadResponse, TokenFlag, ResponseFlags @pytest.fixture @@ -29,8 +29,10 @@ def meta_get( return Value( size=1, value=1, - fetched=1, - last_access=1, + flags=ResponseFlags( + fetched=True, + last_access=1, + ), ) elif key.key.endswith("miss"): return Miss() @@ -38,8 +40,10 @@ def meta_get( return Value( size=1, value=1, - fetched=1, - last_access=9999, + flags=ResponseFlags( + fetched=True, + last_access=9999, + ), ) def meta_multiget( @@ -639,8 +643,10 @@ def test_stale_expires( client.meta_get.side_effect = lambda *args, **kwargs: Value( size=1, value=1, - fetched=1, - last_access=9999, + flags=ResponseFlags( + fetched=True, + last_access=9999, + ), ) # The item will no longer be in the hot cache