Timeouts in Crystal

Timeouts are important for programs that connect to external resources or that otherwise need to bound execution time. Implementing timeouts in Crystal is straightforward using channels and select.

require "time"

def main
  # For our example, suppose we're executing an external
  # call that returns its result on a channel `c1`
  # after 2s. Note that the channel is unbuffered in Crystal,
  # so we need to use a spawn block to prevent blocking.
  c1 = Channel(String).new
  spawn do
    sleep 2.seconds
    c1.send "result 1"
  end

  # Here's the `select` implementing a timeout.
  # `c1.receive` awaits the result and `timeout` awaits
  # a value to be sent after the timeout of 1s.
  # Since `select` proceeds with the first receive that's ready,
  # we'll take the timeout case if the operation takes more than
  # the allowed 1s.
  select
  when res = c1.receive
    puts res
  when timeout(1.second)
    puts "timeout 1"
  end

  # If we allow a longer timeout of 3s, then the receive
  # from `c2` will succeed and we'll print the result.
  c2 = Channel(String).new
  spawn do
    sleep 2.seconds
    c2.send "result 2"
  end
  select
  when res = c2.receive
    puts res
  when timeout(3.seconds)
    puts "timeout 2"
  end
end

main

Running this program shows the first operation timing out and the second succeeding.

$ crystal run timeouts.cr
timeout 1
result 2

In this Crystal version:

  1. We use require "time" to access time-related functionality.

  2. Channels in Crystal are unbuffered by default, so we use spawn blocks to send messages asynchronously.

  3. The select statement in Crystal works similarly to Go, allowing us to wait on multiple channel operations.

  4. Instead of time.After, Crystal provides a timeout macro that can be used directly in select statements.

  5. We define a main function and call it at the end of the file, as Crystal doesn’t have a special main function like Go does.

This example demonstrates how to implement timeouts in Crystal using channels and select, providing a way to bound execution time for operations that might take too long.