{"id":50311822,"url":"https://github.com/zeromq/omq.rb","last_synced_at":"2026-05-28T21:31:02.814Z","repository":{"id":346870790,"uuid":"1190753573","full_name":"zeromq/omq.rb","owner":"zeromq","description":"Pure Ruby ØMQ without the quirks. Async native. Fast.","archived":false,"fork":false,"pushed_at":"2026-05-09T13:34:40.000Z","size":1285,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-22T10:53:53.745Z","etag":null,"topics":["distributed-systems","network-programming","ruby","zmq"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zeromq.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2026-03-24T15:31:23.000Z","updated_at":"2026-05-17T22:34:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zeromq/omq.rb","commit_stats":null,"previous_names":["paddor/omq","zeromq/omq"],"tags_count":55,"template":false,"template_full_name":null,"purl":"pkg:github/zeromq/omq.rb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fomq.rb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fomq.rb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fomq.rb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fomq.rb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zeromq","download_url":"https://codeload.github.com/zeromq/omq.rb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fomq.rb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33627934,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"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":["distributed-systems","network-programming","ruby","zmq"],"created_at":"2026-05-28T21:31:02.176Z","updated_at":"2026-05-28T21:31:02.805Z","avatar_url":"https://github.com/zeromq.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ØMQ — ZeroMQ for Ruby, no C required\n\n[![CI](https://github.com/zeromq/omq/actions/workflows/ci.yml/badge.svg)](https://github.com/zeromq/omq/actions/workflows/ci.yml)\n[![Gem Version](https://img.shields.io/gem/v/omq?color=e9573f)](https://rubygems.org/gems/omq)\n[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)\n[![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%203.3-CC342D?logo=ruby\u0026logoColor=white)](https://www.ruby-lang.org)\n\n\u003e **1.64M msg/s** inproc | **294k msg/s** ipc | **308k msg/s** tcp\n\u003e\n\u003e **8.7 µs** inproc latency | **51 µs** ipc | **64 µs** tcp\n\u003e\n\u003e Ruby 4.0 + YJIT on a Linux VM — see [`bench/`](bench/) for full results\n\n`gem install omq` and you're done. No libzmq, no compiler, no system packages —\njust Ruby talking to every other ZeroMQ peer out there.\n\nØMQ gives your Ruby processes a way to talk to each other — and to anything\nelse speaking ZeroMQ — without a broker in the middle. Same API whether they\nlive in the same process, on the same machine, or across the network.\nReconnects, queuing, and back-pressure are handled for you; you write the\ninteresting part.\n\nNew to ZeroMQ? Start with [GETTING_STARTED.md](doc/GETTING_STARTED.md) — a ~30 min\nwalkthrough of every major pattern with working code.\n\n## Highlights\n\n- **Zero dependencies on C** — no extensions, no FFI, no libzmq. `gem install`\n  just works everywhere\n- **Fast** — YJIT-optimized hot paths, batched sends, GC-tuned allocations,\n  buffered I/O via [io-stream](https://github.com/socketry/io-stream),\n  direct-pipe inproc bypass\n- **[`omq` CLI](https://github.com/paddor/omq-cli)** — a powerful swiss army\n  knife for ØMQ. `gem install omq-cli`\n- **Every socket pattern** — req/rep, pub/sub, push/pull, dealer/router,\n  xpub/xsub, pair, and all draft types\n- **Every transport** — tcp, ipc (Unix domain sockets), inproc (in-process\n  queues)\n- **Async-native** — built on fibers, non-blocking from the ground up\n- **Works outside Async too** — a shared IO thread handles sockets for callers\n  that aren't inside a reactor, so simple scripts just work\n- **Wire-compatible** — interoperates with libzmq, pyzmq, CZMQ, zmq.rs over tcp\n  and ipc\n- **Bind/connect order doesn't matter** — connect before bind, bind before\n  connect, peers come and go. ZeroMQ reconnects automatically and queued\n  messages drain when peers arrive\n\nFor architecture internals, see [DESIGN.md](doc/DESIGN.md).\nIf you're interested in the steps taken to reach the current performance, read [PERFORMANCE.md](doc/PERFORMANCE.md).\n\n## Install\n\nNo system libraries needed — just Ruby:\n\n```sh\ngem install omq\n# or in Gemfile\ngem 'omq'\n```\n\n## Quick Start\n\n### Request / Reply\n\n```ruby\nrequire 'omq'\nrequire 'async'\n\nAsync do |task|\n  rep = OMQ::REP.bind('inproc://example')\n  req = OMQ::REQ.connect('inproc://example')\n\n  task.async do\n    msg = rep.receive\n    rep \u003c\u003c msg.map(\u0026:upcase)\n  end\n\n  req \u003c\u003c 'hello'\n  p req.receive  # =\u003e [\"HELLO\"]\nensure\n  req\u0026.close\n  rep\u0026.close\nend\n```\n\n### Pub / Sub\n\n```ruby\nAsync do |task|\n  pub = OMQ::PUB.bind('inproc://pubsub')\n  sub = OMQ::SUB.connect('inproc://pubsub')\n  sub.subscribe('')  # subscribe to all\n\n  task.async { pub \u003c\u003c 'news flash' }\n  p sub.receive  # =\u003e [\"news flash\"]\nensure\n  pub\u0026.close\n  sub\u0026.close\nend\n```\n\n### Push / Pull (Pipeline)\n\n```ruby\nAsync do\n  push = OMQ::PUSH.connect('inproc://pipeline')\n  pull = OMQ::PULL.bind('inproc://pipeline')\n\n  push \u003c\u003c 'work item'\n  p pull.receive  # =\u003e [\"work item\"]\nensure\n  push\u0026.close\n  pull\u0026.close\nend\n```\n\n### Without Async (IO thread)\n\nOMQ spawns a shared `omq-io` thread when used outside an Async reactor — no\nboilerplate needed:\n\n```ruby\nrequire 'omq'\n\npush = OMQ::PUSH.bind('tcp://127.0.0.1:5557')\npull = OMQ::PULL.connect('tcp://127.0.0.1:5557')\n\npush \u003c\u003c 'hello'\np pull.receive  # =\u003e [\"hello\"]\n\npush.close\npull.close\n```\n\nThe IO thread runs all pumps, reconnection, and heartbeating in the background.\nWhen you're inside an `Async` block, OMQ uses the existing reactor instead.\n\n### Queue Interface\n\nAll sockets expose an `Async::Queue`-inspired interface:\n\n| Async::Queue | OMQ Socket | Notes |\n|---|---|---|\n| `enqueue(item)` / `push(item)` | `enqueue(msg)` / `push(msg)` | Also: `send(msg)`, `\u003c\u003c msg` |\n| `dequeue(timeout:)` / `pop(timeout:)` | `dequeue(timeout:)` / `pop(timeout:)` | Defaults to socket's `read_timeout` |\n| `wait` | `wait` | Blocks indefinitely (ignores `read_timeout`) |\n| `each` | `each` | Yields messages; returns on close or timeout |\n\n```ruby\npull = OMQ::PULL.bind('inproc://work')\n\n# iterate messages like a queue\npull.each do |msg|\n  puts msg.first\nend\n```\n\n## Socket Types\n\nAll sockets are thread-safe. Default HWM is 1000 messages per socket.\n`max_message_size` defaults to **`nil` (unlimited)** — set\n`socket.max_message_size = N` to cap inbound frames at `N` bytes; oversized\nframes cause the connection to be dropped before the body is read from the\nwire. Classes live under `OMQ::` (alias: `ØMQ`).\n\n**Received messages are frozen** across all transports (inproc, ipc, tcp).\nThe array returned by `#receive` and every part inside it is frozen —\nmutating a received part raises `FrozenError` rather than silently\ncorrupting a shared reference on the inproc fast path. `dup` a part if\nyou need to mutate it.\n\n#### Standard (multipart messages)\n\n| Pattern | Send | Receive | When HWM full |\n|---------|------|---------|---------------|\n| **REQ** / **REP** | Work-stealing / route-back | Fair-queue | Block |\n| **PUB** / **SUB** | Fan-out to subscribers | Subscription filter | Drop |\n| **PUSH** / **PULL** | Work-stealing to workers | Fair-queue | Block |\n| **DEALER** / **ROUTER** | Work-stealing / identity-route | Fair-queue | Block |\n| **XPUB** / **XSUB** | Fan-out (subscription events) | Fair-queue | Drop |\n| **PAIR** | Exclusive 1-to-1 | Exclusive 1-to-1 | Block |\n\n\u003e **Work-stealing, not round-robin.** Outbound load balancing uses one shared\n\u003e send queue per socket drained by N racing pump fibers, so a slow peer can't\n\u003e stall the pipeline. Under tight bursts on small `n`, distribution isn't\n\u003e strict RR. See [DESIGN.md](doc/DESIGN.md#per-socket-hwm-not-per-connection) and\n\u003e [Libzmq quirks](doc/DESIGN.md#libzmq-quirks-omq-avoids) for the reasoning.\n\n#### Draft (single-frame only)\n\nBundled with `omq` but not loaded by `require \"omq\"` — opt in with the matching\n`require` line. See [`doc/socket-types/`](doc/socket-types/) for per-pattern\nusage.\n\n| Pattern | Send | Receive | When HWM full | Opt-in `require` |\n|---------|------|---------|---------------|------------------|\n| **CLIENT** / **SERVER** | Work-stealing / routing-ID | Fair-queue | Block | `require \"omq/client_server\"` |\n| **RADIO** / **DISH** | Group fan-out | Group filter | Drop | `require \"omq/radio_dish\"` (also registers `udp://`) |\n| **SCATTER** / **GATHER** | Work-stealing | Fair-queue | Block | `require \"omq/scatter_gather\"` |\n| **PEER** | Routing-ID | Fair-queue | Block | `require \"omq/peer\"` |\n| **CHANNEL** | Exclusive 1-to-1 | Exclusive 1-to-1 | Block | `require \"omq/channel\"` |\n\n## CLI\n\nInstall [omq-cli](https://github.com/paddor/omq-cli) for a command-line tool\nthat sends, receives, pipes, and transforms ZeroMQ messages from the terminal:\n\n```sh\ngem install omq-cli\n\nomq rep -b tcp://:5555 --echo\necho \"hello\" | omq req -c tcp://localhost:5555\n```\n\nSee the [omq-cli README](https://github.com/paddor/omq-cli) for full documentation.\n\n## Optional libzmq backend\n\nOMQ ships with an optional libzmq FFI backend. Same socket API, but\nbacked by libzmq instead of the pure Ruby ZMTP stack. Useful when you\nneed libzmq-specific features or for verifying wire compatibility.\n\n```ruby\nrequire \"omq/ffi\"\npush = OMQ::PUSH.new(backend: :ffi)\n```\n\nRequires the `ffi` gem and a system libzmq 4.x. `ffi` is not a runtime\ndependency of `omq` — install it explicitly (`gem install ffi`) if you\nwant the `:ffi` backend.\n\n## Companion Gems\n\n- **[omq-ractor](https://github.com/paddor/omq-ractor)** — bridge OMQ sockets\n  into Ruby Ractors for true parallel processing across cores. I/O stays on the\n  main Ractor, worker Ractors do pure computation.\n\n### Compressed transports\n\nDrop-in TCP replacements that compress every message part on the wire. Both\npeers must use the same scheme — a `tcp://` peer cannot talk to a\n`zstd+tcp://` peer. The ZMTP handshake itself runs over plain TCP; only\npost-handshake message parts are compressed.\n\n- **[omq-lz4](https://github.com/paddor/omq-lz4)** — `lz4+tcp://`.\n  LZ4 per-part compression. ~4–8× faster encode than zstd, ~16 KiB per\n  connection. Use for CPU- or memory-scarce deployments.\n- **[omq-zstd](https://github.com/paddor/omq-zstd)** — `zstd+tcp://`.\n  Zstandard per-part compression. Best ratio; ~256 KiB encoder state per\n  connection. Use when bandwidth matters more than CPU.\n\n### Protocol extensions (RFCs)\n\nOptional plug-ins that extend the ZMTP wire protocol. Each is a separate gem;\nload the ones you need.\n\n- **[omq-qos](https://github.com/paddor/omq-qos)** — MQTT-style at-least-once\n  delivery. Receivers send an ACK command frame keyed by xxHash digest;\n  unacked messages re-enqueue to the next peer on disconnect. PUSH/PULL,\n  SCATTER/GATHER, REQ/REP only — fan-out patterns are out of scope.\n- **[omq-blake3zmq](https://github.com/paddor/omq-blake3zmq)** — experimental\n  security mechanism replacing CurveZMQ with X25519 + ChaCha20-BLAKE3 AEAD\n  and BLAKE3 transcript hashing. Not audited; use CurveZMQ for production.\n\n## Development\n\n```sh\nbundle install\nbundle exec rake\n```\n\n### Full development setup\n\nSet `OMQ_DEV=1` to tell Bundler to load sibling projects from source\n(protocol-zmtp, omq-zstd ,nuckle, etc.) instead of released gems.\nThis is required for running benchmarks and for testing changes across\nthe stack.\n\n```sh\n# clone OMQ and its sibling repos into the same parent directory\ngit clone https://github.com/paddor/omq.git\ngit clone https://github.com/paddor/protocol-zmtp.git\ngit clone https://github.com/paddor/omq-zstd.git\ngit clone https://github.com/paddor/omq-ractor.git\ngit clone https://github.com/paddor/nuckle.git\n\ncd omq\nOMQ_DEV=1 bundle install\nOMQ_DEV=1 bundle exec rake\n```\n\n## License\n\n[ISC](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzeromq%2Fomq.rb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzeromq%2Fomq.rb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzeromq%2Fomq.rb/lists"}