Creating a UDP server with Ruby Ractors

Claus Lensbøl
2 min readMar 16, 2021

--

Let me start by saying that the influence for this post comes from
Kir Shatrov’s post Writing a Ractor-based web server. You should definitely read that post as well, if you’re interested in Ractors!

What are Ractors in Ruby?

Ruby (MRI) has a Global VM lock. This means that you cannot execute code in multiple threads at the same time. You can achieve a lot of great things with Threads, especially if you have a lot of I/O, but parallel execution in Threads does not exist in Ruby.

Ruby 3.0 introduces Ractors, a concept that lets you create true parallel execution with messages flowing between Ractors instead of shared objects. You can think of a Ractor as an independent worker that you can call up and ask to do a task for you while you do something else.

This feature is experimental in Ruby 3.0 and might have changes in the future. All examples here are with Ruby 3.0.

Coming from TCP

Kir solves the problem of messages in his TCP server by passing the TCP client socket (returned by the #accept method), and “moving” the socket to another Ractor. That works great for TCP, but UDP does not have this concept of a client socket, so how do we get this to work in UDP?

Let’s start by looking at the TCP example.

Here we have three types of Ractors:

  • A listener that opens the TCP socket, listens for and accept incoming connections and moves the client socket to pipe
  • A pipe that takes messages received and yields them to what might be wanting them
  • A number of listener ‘s that pulls client sockets from pipe, reads from the socket, replies, and closes the socket.

This is Kir’s solution (without HTTP):

This approach does not work with UDP as we do not have a client socket to move over the message interface, so how do we solve this?

UDP Ractor solutions

I have found two solutions to this issue:

  • Create the server socket inside the listener, create a socket copy with Object#dup, and pass both this new socket, the received message, and client info to the pipe
  • Create the server socket outside the context of the Ractors, and give the socket as an argument when starting the Ractors. Then pass the received message and client info to the pipe

I like the second solution best as it involves less duplication of objects and skips the step of copying that socket object between the Ractors.

If you know a better way to do this with Ractors, I’d love to hear from you!

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Written by Claus Lensbøl

Telecommunication software engineer who is passionate about machines talking together — https://github.com/cmolhttps://cmol.me

No responses yet

Write a response