{"id":47680069,"url":"https://github.com/zeromq/omq","last_synced_at":"2026-04-17T21:02:14.878Z","repository":{"id":346870790,"uuid":"1190753573","full_name":"zeromq/omq","owner":"zeromq","description":"Pure Ruby ØMQ without the quirks. Async native. Fast.","archived":false,"fork":false,"pushed_at":"2026-04-15T17:16:39.000Z","size":857,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-15T19:18:34.039Z","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-04-15T17:16:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zeromq/omq","commit_stats":null,"previous_names":["paddor/omq","zeromq/omq"],"tags_count":53,"template":false,"template_full_name":null,"purl":"pkg:github/zeromq/omq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fomq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fomq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fomq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fomq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zeromq","download_url":"https://codeload.github.com/zeromq/omq/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zeromq%2Fomq/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31945987,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-17T17:29:20.459Z","status":"ssl_error","status_checked_at":"2026-04-17T17:28:47.801Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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-04-02T13:56:37.150Z","updated_at":"2026-04-17T21:02:14.832Z","avatar_url":"https://github.com/zeromq.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OMQ — Where did the C dependency go!?\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`gem install omq` — that's it. No libzmq, no compiler, no system packages. Just Ruby.\n\nOMQ builds ZeroMQ socket patterns on top of [protocol-zmtp](https://github.com/paddor/protocol-zmtp) (a pure Ruby [ZMTP 3.1](https://rfc.zeromq.org/spec/23/) codec) using [Async](https://github.com/socketry/async) fibers. It speaks native ZeroMQ on the wire and interoperates with libzmq, pyzmq, CZMQ, and everything else in the ZMQ ecosystem.\n\n\u003e **980k msg/s** inproc | **38k msg/s** ipc | **31k msg/s** tcp\n\u003e\n\u003e **10 µs** inproc latency | **71 µs** ipc | **82 µs** tcp\n\u003e\n\u003e Ruby 4.0 + YJIT on a Linux VM — see [`bench/`](bench/) for full results\n\n---\n\n## What is ZeroMQ?\n\nBrokerless message-oriented middleware. No central server, no extra hop — processes talk directly to each other, cutting latency in half compared to broker-based systems. You get the patterns you'd normally build on top of RabbitMQ or Redis — pub/sub, work distribution, request/reply, fan-out — but decentralized, with no single point of failure.\n\nNetworking is hard. ZeroMQ abstracts away reconnection, queuing, load balancing, and framing so you can focus on what your system actually does. Start with threads talking over `inproc://`, split into processes with `ipc://`, scale across machines with `tcp://` — same code, same API, just change the URL.\n\nIf you've ever wired up services with raw TCP, HTTP polling, or Redis pub/sub and wished it was simpler, this is what you've been looking for.\n\nSee [GETTING_STARTED.md](GETTING_STARTED.md) for a ~30 min walkthrough of all major patterns with working code.\n\n## Highlights\n\n- **Zero dependencies on C** — no extensions, no FFI, no libzmq. `gem install` just works everywhere\n- **Fast** — YJIT-optimized hot paths, batched sends, recv prefetching, direct-pipe inproc bypass. 980k msg/s inproc, 10 µs latency\n- **[`omq` CLI](https://github.com/paddor/omq-cli)** — `gem install omq-cli` for a command-line tool with Ruby eval, Ractor parallelism, and script handlers\n- **Every socket pattern** — req/rep, pub/sub, push/pull, dealer/router, xpub/xsub, pair, and all draft types\n- **Every transport** — tcp, ipc (Unix domain sockets), inproc (in-process queues)\n- **Async-native** — built on fibers, non-blocking from the ground up. A shared IO thread handles sockets outside of Async — no reactor needed for simple scripts\n- **Wire-compatible** — interoperates with libzmq, pyzmq, CZMQ over tcp and ipc\n- **Bind/connect order doesn't matter** — connect before bind, bind before connect, peers come and go. ZeroMQ reconnects automatically and queued messages drain when peers arrive\n\nFor architecture internals, see [DESIGN.md](DESIGN.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 boilerplate 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. When 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. `max_message_size` defaults to **`nil` (unlimited)** — set `socket.max_message_size = N` to cap inbound frames at `N` bytes; oversized frames cause the connection to be dropped before the body is read from the wire. Classes live under `OMQ::` (alias: `ØMQ`).\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 vs. round-robin.** libzmq uses strict per-pipe round-robin for outbound load balancing — message N goes to peer N mod K regardless of whether that peer is busy. OMQ uses **work-stealing**: one shared send queue per socket and N pump fibers that race to drain it. Whichever pump is ready next picks up the next batch, so a slow peer can't stall the pipeline. The trade-off: distribution is not strict round-robin under bursts. If a producer enqueues a large burst before any pump fiber gets scheduled, the first pump to wake will dequeue up to one whole batch (256 messages or 512 KB, whichever hits first) in a single non-blocking drain — so a tight `n.times { sock \u003c\u003c msg }` loop on a small `n` may dump everything on one peer. Slow or steady producers don't see this: each pump dequeues one message, writes, re-parks, and the FIFO wait queue gives every pump a fair turn. Burst distribution also evens out once the burst exceeds one pump's batch cap. See [DESIGN.md](DESIGN.md#per-socket-hwm-not-per-connection) for the full reasoning.\n\n#### Draft (single-frame only)\n\nThese require the `omq-draft` gem.\n\n| Pattern | Send | Receive | When HWM full |\n|---------|------|---------|---------------|\n| **CLIENT** / **SERVER** | Work-stealing / routing-ID | Fair-queue | Block |\n| **RADIO** / **DISH** | Group fan-out | Group filter | Drop |\n| **SCATTER** / **GATHER** | Work-stealing | Fair-queue | Block |\n| **PEER** | Routing-ID | Fair-queue | Block |\n| **CHANNEL** | Exclusive 1-to-1 | Exclusive 1-to-1 | Block |\n\n## CLI\n\nInstall [omq-cli](https://github.com/paddor/omq-cli) for a command-line tool that 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## Companion Gems\n\n- **[omq-ffi](https://github.com/paddor/omq-ffi)** — libzmq FFI backend. Same OMQ socket API, but backed by libzmq instead of the pure Ruby ZMTP stack. Useful for interop testing and when you need libzmq-specific features. Requires libzmq installed.\n- **[omq-ractor](https://github.com/paddor/omq-ractor)** — bridge OMQ sockets into Ruby Ractors for true parallel processing across cores. I/O stays on the main Ractor, worker Ractors do pure computation.\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, nuckle, omq-rfc-\\*, 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/nuckle.git\ngit clone https://github.com/paddor/omq-rfc-blake3zmq.git\ngit clone https://github.com/paddor/omq-rfc-channel.git\ngit clone https://github.com/paddor/omq-rfc-clientserver.git\ngit clone https://github.com/paddor/omq-rfc-p2p.git\ngit clone https://github.com/paddor/omq-rfc-qos.git\ngit clone https://github.com/paddor/omq-rfc-radiodish.git\ngit clone https://github.com/paddor/omq-rfc-scattergather.git\ngit clone https://github.com/paddor/omq-ffi.git\ngit clone https://github.com/paddor/omq-ractor.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","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzeromq%2Fomq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzeromq%2Fomq/lists"}