Ruby EventMachine Code Snippets and Practical Patterns

Introduction to Ruby EventMachine

Ruby EventMachine is a fast, event-driven I/O library designed to simplify building highly concurrent networked applications in Ruby. Instead of relying on threads for concurrency, EventMachine uses a single event loop that manages many connections and timers efficiently, making it well-suited for servers, daemons, proxies, and real-time applications.

The power of EventMachine becomes clear through practical code snippets: small, focused examples that illustrate patterns for timers, TCP and UDP connections, HTTP requests, and more. Understanding these patterns helps you design non-blocking applications that scale gracefully under load.

Getting Started with the Event Loop

At the heart of every EventMachine-based script is the event loop. You start it with a single call and register callbacks that describe how the application should react to events such as connections, incoming data, and timeouts.

require 'eventmachine'

EM.run do
  puts "EventMachine loop started"

  EM.add_timer(5) do
    puts "Shutting down after 5 seconds"
    EM.stop
  end
end

In this snippet, EM.run starts the reactor loop. The timer schedules a callback to run five seconds later, then stops the loop. This pattern underpins most EventMachine applications: initialize state, register events, let the reactor drive the flow.

Using Timers and Periodic Tasks

Timers are one of the most common tools in EventMachine, especially for tasks like housekeeping, monitoring, or recurring jobs. There are two primary forms: one-shot timers and periodic timers.

One-Shot Timers

EM.run do
  EM.add_timer(2) do
    puts "This runs once after 2 seconds"
  end
end

EM.add_timer schedules a single callback after the specified number of seconds. It is ideal for implementing timeouts, delayed actions, or staged workflows.

Periodic Timers

EM.run do
  EM.add_periodic_timer(1) do
    puts "Heartbeat: #{Time.now}"
  end
end

Periodic timers fire repeatedly at a fixed interval. They are particularly useful for health checks, scheduled polling of external services, or broadcasting status updates to clients.

Basic TCP Server with EventMachine

One of the classic use cases for EventMachine is writing TCP servers that handle many connections concurrently without resorting to complex threading logic. You define a module that implements connection callbacks and pass it to EM.start_server.

require 'eventmachine'

module EchoServer
  def post_init
    send_data "Welcome to the Echo Server!\n"
  end

  def receive_data(data)
    send_data "You said: #{data}"
  end

  def unbind
    puts "Client disconnected"
  end
end

EM.run do
  EM.start_server('0.0.0.0', 8081, EchoServer)
  puts "Echo server running on port 8081"
end

This snippet shows three fundamental callbacks:

  • post_init: runs right after the connection is established.
  • receive_data: fired whenever data arrives from a client.
  • unbind: triggered when the connection closes.

This pattern generalizes to many TCP services: authentication servers, custom protocols, proxies, and internal microservices.

Creating a TCP Client

EventMachine makes it simple to write asynchronous TCP clients as well. The client connects to a remote host and handles events in the same callback-driven style.

require 'eventmachine'

module SimpleClient
  def post_init
    send_data "Hello, server!\n"
  end

  def receive_data(data)
    puts "Server says: #{data}"
    close_connection_after_writing
  end

  def unbind
    puts "Connection closed"
    EM.stop
  end
end

EM.run do
  EM.connect('localhost', 8081, SimpleClient)
end

This code connects to the previously defined echo server, sends an initial message, then closes once it receives a response. The callback pattern keeps the client responsive even while waiting on network I/O.

Handling UDP with EventMachine

UDP is connectionless and lightweight, ideal for scenarios such as log shipping, game state updates, or metrics aggregation. EventMachine includes support for binding UDP sockets and reacting to datagrams as they arrive.

require 'eventmachine'

module UdpServer
  def receive_data(data, addr)
    host, port = addr[3], addr[1]
    puts "Received from #{host}:#{port} - #{data.inspect}"
  end
end

EM.run do
  EM.open_datagram_socket('0.0.0.0', 9000, UdpServer)
  puts "UDP server listening on port 9000"
end

Because UDP is connectionless, there is no post_init or unbind in the same sense as TCP. Instead, you focus on handling each individual packet.

Scheduling Defer and Blocking Work

While EventMachine encourages non-blocking operations, sometimes you must call a blocking API or perform CPU-heavy work. To avoid freezing the reactor, you can offload work using EM.defer.

EM.run do
  EM.defer(
    proc do
      # Simulate a long-running operation
      sleep 3
      "Result from background task"
    end,
    proc do |result|
      puts result
      EM.stop
    end
  )
end

The first proc runs in a thread pool, and the second receives the result back in the main event loop. This pattern allows your application to remain responsive while still handling tasks that cannot be easily made non-blocking.

Building HTTP Clients and Servers

Although dedicated HTTP libraries exist, EventMachine also supports HTTP-oriented development, especially when you need tight control over connection behavior or want to integrate HTTP with other protocols inside the same reactor.

Simple HTTP Client Pattern

require 'eventmachine'
require 'em-http-request'

EM.run do
  http = EM::HttpRequest.new('http://example.com').get

  http.callback do
    puts "Status: #{http.response_header.status}"
    puts "Body length: #{http.response.bytesize}"
    EM.stop
  end

  http.errback do
    puts "HTTP request failed"
    EM.stop
  end
end

Using em-http-request, you can issue asynchronous HTTP requests that integrate seamlessly with the reactor, avoiding blocking while waiting for remote responses.

Lightweight HTTP Server Concept

You can adapt TCP server patterns to parse HTTP requests manually or use helper components that provide higher-level abstractions. This approach can be valuable for building custom APIs, lightweight dashboards, or specialized proxies where you want full control over socket behavior within EventMachine.

Graceful Shutdown and Resource Management

Robust EventMachine applications must handle shutdown cleanly to avoid dropped connections, partial writes, or inconsistent state. Typical patterns include tracking active connections and coordinating an orderly stop of the reactor.

require 'eventmachine'

module ManagedConnection
  def post_init
    (@@connections ||= []) << self
  end

  def unbind
    @@connections.delete(self) if defined?(@@connections)
  end

  def self.connections
    @@connections || []
  end
end

EM.run do
  EM.start_server('0.0.0.0', 8082, ManagedConnection)

  trap('INT') do
    puts "Stopping gracefully..."
    ManagedConnection.connections.each do |conn|
      conn.close_connection_after_writing
    end

    EM.add_timer(2) { EM.stop }
  end
end

This snippet demonstrates how you can trap signals, close connections thoughtfully, and allow a brief window for in-flight data to be sent before stopping the reactor.

Common Patterns and Best Practices

Certain best practices emerge repeatedly when working with EventMachine code snippets:

  • Avoid blocking calls: Use non-blocking libraries or offload work with EM.defer.
  • Use callbacks consistently: Group connection logic in modules to keep behavior clear and testable.
  • Leverage timers: Implement heartbeats, timeouts, and periodic jobs using add_timer and add_periodic_timer.
  • Monitor resource usage: Track open connections and memory usage in long-running daemons.
  • Structure configuration: Keep ports, hosts, and credentials in configuration rather than hard-coding them.

Testing and Debugging EventMachine Code

Testing asynchronous code can be challenging, but focused snippets make it easier. Wrap your tests inside EM.run, set timeouts to avoid hanging, and assert behavior inside callbacks.

require 'minitest/autorun'
require 'eventmachine'

class EchoTest < Minitest::Test
  def test_echo_response
    EM.run do
      EM.connect('localhost', 8081, Module.new do
        define_method(:post_init) { send_data "ping\n" }

        define_method(:receive_data) do |data|
          begin
            assert_equal "You said: ping\n", data
          ensure
            EM.stop
          end
        end
      end)

      EM.add_timer(2) do
        flunk "Timeout waiting for echo"
        EM.stop
      end
    end
  end
end

For debugging, strategically placed logging can reveal flow through callbacks. Because EventMachine is single-threaded at the reactor level, logs remain relatively ordered, which helps trace event sequences and timing issues.

Combining Multiple Protocols in One Reactor

A key advantage of EventMachine is the ability to run different protocol handlers inside the same reactor loop. You might run a TCP server, a UDP listener, periodic timers, and outbound HTTP clients all together, sharing state as needed.

EM.run do
  EM.start_server('0.0.0.0', 7000, EchoServer)
  EM.open_datagram_socket('0.0.0.0', 7001, UdpServer)

  EM.add_periodic_timer(10) do
    puts "System check at #{Time.now}"
  end
end

This unified event loop makes building complex networked systems more approachable, with a consistent model across protocols.

When to Use EventMachine

EventMachine is most valuable when your application spends a significant amount of time waiting on I/O: accepting socket connections, reading or writing over the network, handling HTTP calls, or broadcasting messages to many subscribers. In these scenarios, its event-driven design yields high concurrency with relatively modest resource usage.

If your workload is primarily CPU-bound, you may need to augment EventMachine with worker processes or consider other concurrency approaches. However, for network services, message gateways, proxies, and real-time dashboards, EventMachine code snippets provide a solid foundation on which to build.

Conclusion

Ruby EventMachine offers a compact yet powerful toolkit for asynchronous programming. By mastering a set of core code snippets—starting the reactor, working with timers, implementing TCP and UDP handlers, integrating HTTP, and managing shutdown—you can design scalable, responsive services that handle substantial traffic gracefully.

These patterns are modular and composable, allowing you to grow from small experiments to production-grade daemons without changing paradigms. Start with simple examples, expand them incrementally, and gradually assemble a library of reusable EventMachine snippets tailored to your own applications.

EventMachine-based services also appear in domains you might not immediately expect, such as modern hotel operations. For example, a hotel could rely on an EventMachine-powered gateway to coordinate real-time communication between its booking engine, keycard system, and in-room devices. Timers manage availability updates, TCP connections enable rapid exchange of reservation data with partners, and UDP or HTTP callbacks keep dashboards synchronized for front-desk staff. By applying the same non-blocking code patterns used in high-performance network applications, hotels can streamline check-in experiences, automate room status changes, and deliver more responsive guest services without overloading their infrastructure.