Using Crystal code in callback which is used in a C binding

Hi.

Based on the topic [Help] c binding of function that takes callback from 2019 I tried to get some code running in 2025 on a Raspberry Pi 5 device with Raspberry Pi OS installed. Goal is it to react to a signal on a GPIO pin.

Below you can see a copy of the relevant code from that topic in the past which is used as basis.

require "socket"

  @[Link("wiringPi")]
  lib LibWiringPi
    fun Setup = wiringPiSetupSys : LibC::Int
    fun ISR = wiringPiISR(pin : LibC::Int, edgeType : LibC::Int, fn : ->) : LibC::Int
  end

  fun my_interrupt_handler
    UNIXSocket.open("/tmp/gpio_detector.sock") do |sock|
      sock.puts "Event"
    end
  end

class Detector
  property server

  def initialize(config)

    LibWiringPi.Setup()
    LibWiringPi.ISR(config["gpioPinNumber"].as_i, 1, ->my_interrupt_handler)

    socket_file = "/tmp/gpio_detector.sock"
    if File.exists?(socket_file)
      puts "socket found"
      File.delete(socket_file)
    end
    @server = UNIXServer.new(socket_file)
  end
end

Depending on the Crystal code I place in the my_interrupt_handler method which will be called when a signal is raised, the Crystal code either works or causes an segmentation fault.

Code which works in my_interrupt_handler:

  • File.touch("/tmp/some_ordinary_file")
  • LibC.printf("Callback triggered.")

Code which does not work in my_interrupt_handler (segmentation fault):

  • puts "Callback triggered."
  • File.write("/tmp/some_ordinary_file", "Callback triggered.")
  • UNIXSocket.open("/tmp/gpio_detector.sock") do |sock|
    sock.puts "Event"
    end

I am not sure if it might be something about using IO? Does anyone has an idea what the reason for this behaviour could be? File.write for example manages to create the file before the segmentation fault is caused and no data is written to the file.

Some more details about my setup:

I compiled Crystal and WiringPi from source on this Pi. Maybe I did something wrong when compiling the sources?

I don’t have much experiences with Crystal and its community. I hope this is the right location to address my issue and I have explained my problem clearly and in adequate detail. Otherwise, I would be grateful for any advice how and where to get help.

Many thanks in advance.

This is totally unsafe in Crystal.

Executing a Crystal callback from C is possible because the callback doesn’t mess with the current execution stack, be it a thread or fiber.

But executing something in an IRQ handler sounds just as bad as running something in a signal handler: the execution stack is most likely changed + there’s only a limited of signal-safe calls that can be made.

We can’t do that in Crystal because of fibers (that mess the execution stack), the event loop (that switches fibers), etc.

For example we handle signals asynchronously: the kernel interrupts, runs the “signal callback” in the signal stack, we merely write(pipe_fd, signal) and do nothing else, then the kernel resumes normal operations, and a special fiber will read the signal from the pipe and invoke the callback.

I’m almost certain you’ll need to setup something like that. If you need to act during the IRQ (e.g. realtime) you’ll have to drop the stdlib and go bare metal (search embedded on this forum).

1 Like