{"id":20763789,"url":"https://github.com/revenuecat/meta-memcache-socket-py","last_synced_at":"2026-04-20T08:02:20.912Z","repository":{"id":205776538,"uuid":"715054924","full_name":"RevenueCat/meta-memcache-socket-py","owner":"RevenueCat","description":"Helpers for meta-memcache-py, implemented in rust for improved performance.","archived":false,"fork":false,"pushed_at":"2026-04-08T11:22:13.000Z","size":92,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":11,"default_branch":"main","last_synced_at":"2026-04-08T13:15:02.035Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RevenueCat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-11-06T11:46:09.000Z","updated_at":"2026-04-08T11:21:39.000Z","dependencies_parsed_at":"2023-11-22T12:27:27.150Z","dependency_job_id":"eeee63f8-de84-41ee-be7f-2a967da25e54","html_url":"https://github.com/RevenueCat/meta-memcache-socket-py","commit_stats":null,"previous_names":["revenuecat/meta-socket-py","revenuecat/meta-memcache-socket-py"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/RevenueCat/meta-memcache-socket-py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RevenueCat%2Fmeta-memcache-socket-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RevenueCat%2Fmeta-memcache-socket-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RevenueCat%2Fmeta-memcache-socket-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RevenueCat%2Fmeta-memcache-socket-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RevenueCat","download_url":"https://codeload.github.com/RevenueCat/meta-memcache-socket-py/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RevenueCat%2Fmeta-memcache-socket-py/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32038455,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-20T00:18:06.643Z","status":"online","status_checked_at":"2026-04-20T02:00:06.527Z","response_time":94,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-17T10:47:32.725Z","updated_at":"2026-04-20T08:02:20.904Z","avatar_url":"https://github.com/RevenueCat.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# meta-memcache-socket\n\nA high-performance Rust extension for Python that provides socket I/O, command\nbuilding, and response parsing for the\n[Memcache meta-protocol](https://github.com/memcached/memcached/wiki/MetaCommands).\nDesigned as the low-level transport layer for\n[meta-memcache-py](https://github.com/RevenueCat/meta-memcache-py).\n\n## Key features\n\n- **Rust-native socket I/O** — direct `send()`/`recv()`/`poll()` syscalls,\n  bypassing Python's socket layer while still respecting `settimeout()`\n- **GIL-free** — releases the GIL during all socket operations (`py.detach()`),\n  so other Python threads run freely while waiting on the network\n- **Zero-copy where possible** — response values are read directly into the\n  internal buffer; `PyBytes` is created from the buffer slice without\n  intermediate allocation\n- **SIMD-accelerated parsing** — uses `memchr` for fast `\\r\\n` scanning\n- **Free-threaded Python support** — built with `gil_used = false`, compatible\n  with Python 3.13t (no-GIL builds)\n\n## Project structure\n\n```\nmeta-memcache-socket-py/\n├── Cargo.toml                      # Rust package manifest\n├── pyproject.toml                  # Python package manifest (maturin backend)\n├── src/\n│   ├── lib.rs                      # PyO3 module entry — exports classes, functions, constants\n│   ├── constants.rs                # Protocol constants (response codes, set modes, NOOP, ENDL)\n│   ├── memcache_socket.rs          # MemcacheSocket class — socket I/O, buffering, GIL management\n│   ├── request_flags.rs            # RequestFlags class — immutable flags for building commands\n│   ├── response_flags.rs           # ResponseFlags class — immutable flags parsed from responses\n│   ├── response_types.rs           # Response type classes (Value, Success, Miss, NotStored, Conflict)\n│   ├── impl_build_cmd.rs           # Command builder — key validation, base64, flag encoding\n│   ├── impl_parse_header.rs        # Header parser — SIMD search, flag parsing, atoi\n│   ├── impl_build_cmd_tests.rs     # Rust unit tests for command building\n│   ├── impl_parse_header_tests.rs  # Rust unit tests for header parsing\n│   ├── request_flags_tests.rs      # Rust unit tests for RequestFlags\n│   └── response_flags_tests.rs     # Rust unit tests for ResponseFlags\n├── tests/\n│   ├── test_memcache_socket.py     # Python tests — socket I/O, timeouts, buffering, NOOP\n│   └── test_response_types.py      # Python tests — response type semantics\n├── bench.py                        # Microbenchmarks for command building and header parsing\n└── .github/workflows/CI.yml        # CI — Rust tests, Python tests, cross-platform wheel builds\n```\n\n## Design overview\n\n### Architecture\n\nThe module is a single Rust cdylib compiled with [PyO3](https://pyo3.rs/) and\npackaged with [Maturin](https://www.maturin.rs/). There is no Python source\ncode — everything is implemented in Rust and exported to Python directly.\n\nThe design separates into three layers:\n\n1. **Protocol layer** (`constants.rs`, `impl_build_cmd.rs`,\n   `impl_parse_header.rs`) — stateless functions that build command byte strings\n   and parse response headers. These know the meta-protocol grammar but nothing\n   about sockets.\n\n2. **Type layer** (`request_flags.rs`, `response_flags.rs`,\n   `response_types.rs`) — Python-visible classes that carry request parameters\n   and parsed response data.\n\n3. **I/O layer** (`memcache_socket.rs`) — the `MemcacheSocket` class that owns\n   a raw file descriptor, an internal read buffer, and a NOOP counter. All\n   socket operations release the GIL via `py.detach()` and use `poll()` to\n   handle non-blocking sockets with proper timeout support.\n\n### MemcacheSocket internals\n\n```\n┌──────────────────────────────────────────────────────────┐\n│ MemcacheSocket (Python-visible)                          │\n│  • _conn: Py\u003cPyAny\u003e   — prevents Python GC of socket    │\n│  • version: u8         — server version for compat       │\n│  • io: SocketIO        — all I/O state (Send + Ungil)    │\n│    ├── fd: RawFd                                         │\n│    ├── buf: Vec\u003cu8\u003e    — ring buffer for recv'd data     │\n│    ├── pos / read      — read cursor / write cursor      │\n│    ├── timeout_ms      — poll() timeout from settimeout  │\n│    └── noop_expected   — pending NOOP responses to drain  │\n└──────────────────────────────────────────────────────────┘\n```\n\nThe `SocketIO` struct contains no Python objects, so it satisfies PyO3's\n`Ungil` trait and can be passed to `py.detach()` closures that release the GIL.\n\n**Buffer management**: the internal buffer acts as a sliding window. When `pos`\npasses 75% of the buffer, remaining data is shifted to the front\n(`copy_within`). Values that fit in the buffer are served directly from it\n(zero-copy to Rust); values exceeding the buffer are allocated into a temporary\n`Vec`.\n\n**NOOP handling**: when `sendall()` is called with `with_noop=True`, a `mn\\r\\n`\ncommand is appended. The NOOP counter increments. On the next `get_response()`,\nall responses before the corresponding `MN` are drained automatically, enabling\npipelined fire-and-forget commands.\n\n**Timeout handling**: at construction time (and on `set_socket()`), the Python\nsocket's `gettimeout()` is read and converted to milliseconds for `poll()`. If\nthe socket is blocking (`gettimeout()` returns `None`), poll uses `-1`\n(infinite). If a timeout is set, poll respects it and raises Python's\n`TimeoutError` on expiry.\n\n## API reference\n\n### MemcacheSocket\n\nThe main class for socket communication with a Memcache server.\n\n```python\nfrom meta_memcache_socket import MemcacheSocket\n\n# Constructor\nms = MemcacheSocket(\n    conn,                        # Python socket object\n    buffer_size=4096,            # Internal read buffer size in bytes\n    version=SERVER_VERSION_STABLE,  # Server version for protocol compat\n)\n\n# Send data, optionally appending a NOOP command\nms.sendall(data: bytes, with_noop: bool)\n\n# Read and parse the next response header\n# Returns one of: Value, Success, Miss, NotStored, Conflict\nresp = ms.get_response()\n\n# Read value payload (call after get_response() returns a Value)\ndata: bytes = ms.get_value(resp.size)\n\n# Replace the underlying socket (e.g. after reconnect)\nms.set_socket(new_conn)\n\n# Close the underlying socket\nms.close()\n\n# Server version\nms.get_version()  # -\u003e int\n```\n\n### Response types\n\nAll response types are returned by `get_response()`:\n\n| Class | Protocol code | Bool | Fields |\n|---|---|---|---|\n| `Miss` | `EN`, `NF` | `False` | — |\n| `NotStored` | `NS` | `False` | — |\n| `Conflict` | `EX` | `False` | — |\n| `Success` | `HD`, `OK` | `True` | `flags: ResponseFlags` |\n| `Value` | `VA` | `True` | `size: int`, `flags: ResponseFlags`, `value: Any` (settable) |\n\n`Miss`, `NotStored`, and `Conflict` are frozen and support equality.\n`Value.value` is a mutable slot used by higher-level code (e.g. meta-memcache-py's\nexecutor) to attach deserialized data.\n\n### ResponseFlags\n\nImmutable (frozen) container for flags parsed from a server response.\n\n```python\nflags.cas_token     # Optional[int] — CAS token (c)\nflags.fetched       # Optional[bool] — fetched from cache (h)\nflags.last_access   # Optional[int] — seconds since last access (l)\nflags.ttl           # Optional[int] — TTL in seconds, -1 = no expiry (t)\nflags.client_flag   # Optional[int] — user-defined flag (f)\nflags.win           # Optional[bool] — True=W (won), False=Z (lost)\nflags.stale         # bool — marked stale (X)\nflags.size          # Optional[int] — value size (s)\nflags.opaque        # Optional[bytes] — echoed opaque data (O)\n```\n\n### RequestFlags\n\nImmutable container for flags sent with commands.\n\n```python\nfrom meta_memcache_socket import RequestFlags\n\nflags = RequestFlags(\n    # Boolean flags\n    no_reply=False,           # q — don't expect a response\n    return_client_flag=True,  # f\n    return_cas_token=True,    # c\n    return_value=True,        # v\n    return_ttl=False,         # t\n    return_size=False,        # s\n    return_last_access=False, # l\n    return_fetched=False,     # h\n    return_key=False,         # k\n    no_update_lru=False,      # u\n    mark_stale=False,         # I\n\n    # Optional value flags\n    cache_ttl=3600,           # T — TTL in seconds\n    recache_ttl=None,         # R — recache window\n    vivify_on_miss_ttl=None,  # N — create-on-miss TTL\n    client_flag=42,           # F — user-defined flag\n    ma_initial_value=None,    # J — arithmetic initial value\n    ma_delta_value=None,      # D — arithmetic delta\n    cas_token=None,           # C — CAS token for conditional ops\n    opaque=None,              # O — opaque data echoed back\n    mode=None,                # M — operation mode (set/arithmetic)\n)\n```\n\nThe flags are immutable, so they can be reused safely across threads when\ncalling meta commands. Internal layers migth need to mutate flags\n(content id, reduce ttl, etc...) and will mutate them use replace() to create\nmodified copies when needed.\n\nIf you need to change flags on a existing RequestFlags, use the `replace()` method:\n\n```python\nnew_flags = flags.replace(return_ttl=True, cache_ttl=600)  # -\u003e RequestFlags\n```\n\nYou can also encode the flags into a byte string for command building, showing\nexactly what will be sent on the wire:\n\n```python\nflags.to_bytes()   # -\u003e bytes (encoded flag string)\n```\n\nFor debugging purposes, stringifying it shows the flags in a human-readable format.\n\n### Command builders\n\nConvenience functions that build meta-protocol command byte strings.\nAll raise `ValueError` if the key exceeds the length limit (250 bytes, or\n187 for binary keys which are base64-encoded with a `b` flag).\n\n```python\nfrom meta_memcache_socket import (\n    build_meta_get,\n    build_meta_set,\n    build_meta_delete,\n    build_meta_arithmetic,\n    build_cmd,\n)\n\n# mg key [flags]\\r\\n\ncmd = build_meta_get(key: bytes, request_flags=None)\n\n# ms key size [flags]\\r\\n\ncmd = build_meta_set(key: bytes, size: int, request_flags=None, legacy_size_format=False)\n\n# md key [flags]\\r\\n\ncmd = build_meta_delete(key: bytes, request_flags=None)\n\n# ma key [flags]\\r\\n\ncmd = build_meta_arithmetic(key: bytes, request_flags=None)\n\n# Generic: {cmd} key [size] [flags]\\r\\n\ncmd = build_cmd(cmd: bytes, key: bytes, size=None, request_flags=None, legacy_size_format=False)\n```\n\n### parse_header\n\nLow-level function to parse a response header from a buffer. Primarily used\ninternally by `MemcacheSocket.get_response()`, but exposed for advanced use.\n\n```python\nfrom meta_memcache_socket import parse_header\n\n# Returns (end_pos, response_type, size, flags) or None if header is incomplete\nresult = parse_header(\n    buffer: Union[memoryview, bytearray],\n    start: int,\n    end: int,\n)\n```\n\n### Constants\n\n```python\n# Response type codes\nRESPONSE_VALUE = 1\nRESPONSE_SUCCESS = 2\nRESPONSE_NOT_STORED = 3\nRESPONSE_CONFLICT = 4\nRESPONSE_MISS = 5\nRESPONSE_NOOP = 100\n\n# Set modes (for RequestFlags.mode)\nSET_MODE_SET = 83       # 'S' — default set\nSET_MODE_ADD = 69       # 'E' — add (only if not exists)\nSET_MODE_REPLACE = 82   # 'R' — replace (only if exists)\nSET_MODE_APPEND = 65    # 'A' — append to value\nSET_MODE_PREPEND = 80   # 'P' — prepend to value\n\n# Arithmetic modes\nMA_MODE_INC = 43        # '+' — increment\nMA_MODE_DEC = 45        # '-' — decrement\n\n# Server versions\nSERVER_VERSION_AWS_1_6_6 = 1   # AWS ElastiCache 1.6.6 compat\nSERVER_VERSION_STABLE = 2      # Standard memcached\n```\n\n## Development\n\n### Prerequisites\n\n- [Rust](https://rustup.rs/) (stable toolchain, edition 2024)\n- Python \u003e= 3.10\n- [uv](https://docs.astral.sh/uv/) (recommended) or pip + maturin\n\n### Building\n\n```bash\n# Build and install into the project venv (development mode)\nuv run --with maturin maturin develop\n\n# Build in release mode (optimized)\nuv run --with maturin maturin develop --release\n```\n\n### Running tests\n\n**Rust unit tests** — tests command building, header parsing, and flag encoding:\n\n```bash\ncargo test\n```\n\n**Python integration tests** — tests socket I/O, timeouts, buffering, response\ntypes, and NOOP handling using real socket pairs:\n\n```bash\n# Build the extension, then run pytest\nuv run --with maturin maturin develop\nuv run --with pytest pytest tests/ -v\n```\n\n### Running benchmarks\n\n```bash\nuv run --with maturin maturin develop --release\nuv run python bench.py\n```\n\n## Using a local build with meta-memcache-py\n\nWhen developing this package alongside\n[meta-memcache-py](https://github.com/RevenueCat/meta-memcache-py), you need\nmeta-memcache-py to use your local build instead of the PyPI version.\n\n### Option 1: pip install from local path (quick iteration)\n\n```bash\ncd /path/to/meta-memcache-py\n\n# Install the local build (--reinstall forces replacement of the existing version)\nuv pip install -n -v /path/to/meta-memcache-socket-py --reinstall\n```\n\nNOTE: When using this option, any `uv run` will revert the package to the\nversion specified in the pyproject.toml file.\n\n### Option 2: pyproject.toml dependency override (persistent)\n\nIn `meta-memcache-py`'s `pyproject.toml`, replace the PyPI dependency with a\nlocal file reference:\n\n```toml\ndependencies = [\n    # \"meta-memcache-socket\u003e=2.0.0\",  # PyPI version (commented out)\n    \"meta-memcache-socket @ file:///path/to/meta-memcache-socket-py\",\n]\n```\n\nThen sync the environment:\n\n```bash\nuv sync\n```\n\nRemember to revert this before committing.\n\n## Releasing\n\nReleases are automated via GitHub Actions CI.\n\n### Process\n\n1. Update the version in `Cargo.toml`:\n   ```toml\n   [package]\n   version = \"2.1.0\"\n   ```\n\n2. Commit and push to `main`.\n\n3. Create and push a git tag:\n   ```bash\n   git tag v2.1.0\n   git push origin v2.1.0\n   ```\n\n4. The CI pipeline will:\n   - Run Rust and Python tests\n   - Build wheels for all platforms:\n     - Linux: x86_64, x86, aarch64, armv7, s390x, ppc64le (glibc + musl)\n     - macOS: x86_64 (Intel), aarch64 (Apple Silicon)\n     - Windows: x64, x86\n   - Build for both CPython 3.x and free-threaded 3.13t\n   - Generate build provenance attestation\n   - Publish all wheels + sdist to PyPI\n\nThe PyPI upload uses the `PYPI_API_TOKEN` repository secret.\n\n### Manual trigger\n\nThe release job can also be triggered manually via GitHub's \"Run workflow\"\nbutton on the CI workflow page (`workflow_dispatch`). This runs all build jobs\nand generates artifacts but only publishes to PyPI if a tag is present.\n\n## Dependencies\n\n| Crate | Purpose |\n|---|---|\n| [pyo3](https://pyo3.rs/) 0.28 | Python ↔ Rust bindings, GIL management |\n| [libc](https://docs.rs/libc) | Direct syscalls: `poll`, `send`, `recv`, `writev`, `setsockopt` |\n| [memchr](https://docs.rs/memchr) | SIMD-accelerated `\\r\\n` scanning |\n| [atoi](https://docs.rs/atoi) | Fast ASCII → integer for header parsing |\n| [itoa](https://docs.rs/itoa) | Fast integer → ASCII for command building |\n| [base64](https://docs.rs/base64) | Binary key encoding |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frevenuecat%2Fmeta-memcache-socket-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frevenuecat%2Fmeta-memcache-socket-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frevenuecat%2Fmeta-memcache-socket-py/lists"}