Open-source project
socketry/async avatar
socketry/async

socketry/async: a fiber-based asynchronous I/O reactor for Ruby

An awesome asynchronous event-driven reactor for Ruby.

2,460 stars111 forksRubyMIT

At a glance

What is it?
Async is a composable event-driven I/O framework for Ruby built on io-event, using lightweight fibers instead of callbacks. It suits Ruby services that need thousands of concurrent connections per process, but it is not a drop-in replacement for every blocking library.
Who is it for?
Adopt socketry/async if you are writing a Ruby network service, client, or background worker that spends its time waiting on sockets and you want fiber-based concurrency rather than callback chains. Do not adopt it if your workload is CPU-bound, or if your dependencies block the thread with native code that the scheduler cannot see.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 16 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What socketry/async solves, and who it is aimed at

Ruby's default concurrency story is threads. A thread-per-connection server pays for that with memory and scheduler overhead, and a thread that blocks on a socket read is a thread doing nothing useful. Async replaces that model with an event-driven reactor: one thread runs many fibers, and when a fiber would block on I/O, the reactor parks it and runs something else. The README states the goal directly: scalable event-driven I/O for Ruby, with thousands of clients per process, and lightweight fiber-based concurrency so that you do not write callbacks.

The audience is Ruby engineers building network-facing software. The README points to an ecosystem rather than a single library: async-http for HTTP client and server work, falcon as a Rack-compatible server built on async-http, async-websocket for websockets, and async-dns for DNS resolution. If you are writing a crawler, a proxy, a chat backend, or a service that fans out to many upstream APIs, that stack is aimed at you. If you are writing a Rails CRUD app with an ActiveRecord-heavy request path, the reactor will not help much on its own, because the benefit comes from non-blocking I/O all the way down.

How the reactor, scheduler and tasks fit together

The core is a reactor backed by io-event, a separate gem that provides the platform event loop. Ruby's fiber scheduler interface is the hook: when a fiber performs a blocking operation such as a socket read, the scheduler intercepts it, registers interest with the event loop, and yields. When the event loop reports the descriptor is ready, the fiber is resumed. That is why the README can promise no callbacks. The code reads top to bottom like synchronous code.

Above the scheduler sit tasks. A task is a unit of work the reactor schedules, and tasks form a tree, which matters for cancellation. The v2.43.0 release notes say cancellation causes now propagate through task trees so child tasks observe the original cause. That design choice is worth noticing: cancellation is not a flag you poll, it is an exception travelling down a parent-child relationship. The v2.41.0 notes mention protecting the initial task from Interrupt exceptions, which tells you the top of that tree is treated specially.

Synchronisation primitives are part of the same model. Async::Condition lets one task wait for another to signal it, and v2.40.0 added Async::Condition#waiting_count so you can see how many tasks are currently waiting. Async::Barrier is the join primitive: v2.39.0 changed Barrier#wait to return the number of tasks waited for, or nil when there were none. The release history around barriers is instructive. v2.38.1 fixed a case where Barrier#async could race with a parent that yields before the child block executes, causing Barrier#wait to return early and miss the task. These are the kinds of bugs that show up in any task-tracking abstraction, and their presence in the changelog is a fair signal of where the sharp edges are.

Installing async and running your first reactor

The gem is published as async and the README points to the Getting Started guide at socketry.github.io/async for adding it to a project. The conventional route is a Gemfile entry followed by bundle install. The README does not print the Gemfile line itself, so check the Getting Started guide for the exact declaration before adding it.

After that, the smallest useful program wraps work in Async and uses Async::Task#sleep or an I/O call to hand control back to the reactor. The repository ships an examples/ directory with self-contained cases, including examples/buffer/, examples/bugs/, examples/callback/, examples/capture/, examples/count/, examples/dataloader/, examples/debug/, examples/dining-philosophers/, examples/hup-test/, examples/load/, examples/queue/ and examples/stop/. Those directories are the fastest way to see idiomatic usage without reading the guides end to end.

If you are working on the library itself rather than consuming it, the README gives the test command and the release command directly.

shell
bundle exec sus
shell
bundle exec bake gem:release:patch # or minor or major

The first runs the test suite. The second cuts a release. One behavioural detail from the changelog that affects first-run experience: v2.42.0 made Sync and Async callable from a non-blocking fiber with no scheduler, such as inside an Enumerator or a bare Fiber.new. Before that, doing so raised RuntimeError: Running scheduler on non-blocking fiber!. If you are on an older release and see that error, the fix is to upgrade rather than to restructure your code.

Where async is the wrong tool

The reactor only helps when the blocking work is visible to the fiber scheduler. Native extensions that block a thread without going through Ruby's I/O hooks will stall every fiber sharing that thread. That is a property of the scheduler interface, not a defect in Async, but it means a CPU-heavy JSON parse or an image resize in the middle of a request path blocks unrelated connections. Async does not make CPU-bound work faster; the README's own framing is I/O concurrency, and multi-thread or multi-process containers are listed as the route to parallelism, not the reactor itself.

Thread safety is the second boundary. The README links a dedicated Thread safety guide covering fibers and threads, common pitfalls, and problems like data corruption, race conditions and deadlocks. The existence of that guide is a signal: mixing fibers with threads and shared mutable state is where users get hurt, and the documentation treats it as a topic that needs its own page rather than a footnote.

The third boundary is ecosystem coverage. If the library you depend on has no async-compatible variant, you either accept a blocking call or rewrite around it. The README's See Also list is short and specific, which is honest, but it also tells you the compatible surface is narrower than Ruby's gem ecosystem as a whole. A team that cannot change its HTTP or DNS layer should not start here.

How async differs from threads and from async-http's server layer

The obvious alternative is plain Ruby threads with a thread pool. The difference is in the cost model. A thread carries a native stack and is scheduled by the operating system; a fiber is a user-space construct the reactor switches cooperatively. For a workload dominated by waiting on sockets, the fiber model lets one process hold far more concurrent connections, which is the README's thousands-of-clients claim. For a workload dominated by computation, threads win, because they can actually run in parallel on multiple cores while a single reactor cannot.

A second comparison is within the same ecosystem. Async is the reactor and task layer; async-http is the HTTP client and server built on top of it; falcon is a Rack-compatible server built on async-http. If what you need is a web server, you install falcon, not async directly. If you need an HTTP client inside a reactor you already run, you add async-http. Reaching for the reactor gem alone when a higher-level component already exists means writing protocol code the ecosystem has already written. The README's See Also section is effectively a map of which layer to pick.

There is also a comparison to make against callback-style event loops in other languages. Async's pitch is that fiber-based concurrency lets you write sequential-looking code, avoiding callback nesting. That is a readability argument, and it holds up in the examples the repository ships, but it does not change the underlying execution model: you still cannot block, and you still have to reason about where your code yields.

Maintenance, releases and what the MIT licence means here

The repository is not archived, and the last push was on 2026-09-14. The release cadence visible in the changelog is high: v2.46.0, v2.45.1 and v2.45.0 all landed within roughly three weeks of each other. That matters for upgrade cost in both directions. A fast cadence means scheduler and barrier bugs get fixed quickly, as the v2.45.1 fix for Scheduler#io_wait returning nil instead of false shows. It also means a pinned version can fall behind a meaningful fix in a matter of weeks, and the changelog entries are written at a level of detail that assumes you care about the difference between nil and false in a timeout path.

The cost of upgrading is not just running bundle update. Because Async sits under your I/O stack, a scheduler regression can surface as a timeout, a hang, or a TypeError from a native caller, which is exactly the failure mode v2.45.1 describes: Socket#connect with connect_timeout: checks for false to detect a timeout, so a nil return produced TypeError: no implicit conversion from nil to integer instead of the intended IO::TimeoutError. Budget for integration tests that exercise connection timeouts and cancellation, not just happy-path requests.

The project is MIT licensed. In practical terms that is a permissive licence with minimal obligations, but the repository also states that contributors must comply with the Developer Certificate of Origin so that contributions are properly licensed and attributed. If you are evaluating the licence for a commercial product, read the licence text in license.md and the DCO policy yourself; this article is not legal advice.

Editorial conclusion

Adopt socketry/async if you are writing a Ruby network service, client, or background worker that spends its time waiting on sockets and you want fiber-based concurrency rather than callback chains. Do not adopt it if your workload is CPU-bound, or if your dependencies block the thread with native code that the scheduler cannot see. Before committing, verify that your HTTP, database, and DNS libraries have async-compatible variants, and run your own load test against the connection counts you actually expect.

Frequently asked questions

How do I install socketry/async in a Ruby project?

Add the async gem to your Gemfile and run bundle install. The README points to the Getting Started guide at socketry.github.io/async for the full setup walkthrough.

What is socketry/async used for?

It is a composable asynchronous I/O framework for Ruby built on io-event, providing an event-driven reactor with fiber-based concurrency. The README lists scalable event-driven I/O, lightweight fibers without callbacks, and multi-thread or multi-process containers for parallelism as its features.

How is async different from synchronous Ruby code?

In synchronous Ruby, a blocking socket read stops the thread. With Async, the fiber scheduler parks the fiber at that point and the reactor runs other tasks, then resumes it when the descriptor is ready. The README frames this as scalable event-driven I/O with thousands of clients per process.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. socketry/async on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/socketry-async.svg)](https://hysenlabs.com/projects/socketry-async)