Bringing io_uring to Ponylang

Matthias Wahl

Agenda

  1. io_uring
  2. The Ponylang Runtime
  3. The Ponylang async IO subsystem
  4. Incompatibilities
  5. pony-uring

io_uring

New Linux interface for doing syscalls

and get notified upon their completion

The Ponylang Runtime

The Ponylang Runtime

 

 


              actor Actor
                let name: String

                new create(name': String, colleages: Array[Actor] val) =>
                  name = name'
                  for a in colleagues do
                    a.say_line(name + " is way better than me.")
                  end

                be say_line(line: String) =>
                  Debug("[" + name + "] " + line)

              actor Main
                new create(env: Env) =>
                  let monroe = Actor.create("Marylin", [])
                  let reeves = Actor.create("Keanu", [monroe])
            

Design goals

  • High performance actor system
  • Memory-safety and data-race freedom

The Ponylang async IO subsystem

Incompatibilities

  • epoll is thread-safe, io_uring is not
  • async IO subsystem is limited to read/write readiness notifications

pony-uring

  • Initial goal: Add additional asio backend using io_uring
  • Dead end

Alternative

Pony Userland io_uring driver

  • Thread safety: Encapsulate ring access inside one actor
  • Limited asio API: Encapsulate every possible io_uring operation in a class
  • Callback into actor with completion item
  • Upholds Pony safety guarantees across FFI boundary

 

 


              actor Main is URingNotify
                let _env: Env
                let _ring: URing

                new create(env: Env) =>
                  _env = env
                  _ring =
                    InitUring(
                      where entries = 128,
                      flags = SetupFlags.create().add(SetupSqPoll)
                    )?
                  let buf = recover iso Array[U8].create(4096) end

                  // read from stdin
                  let readv_op = OpReadv.from_single_buf(buf where fd' = 0, offset' = 0)
                  _ring.submit_op(read_op, this)
            

 

 


              be op_completed(op: UringOp iso, result I32) =>
                match op
                | let readv_op: OpReadv iso =>
                  let buf = readv_op.extract_buf()(0)?
                  buf.trim_in_place(0, result.usize()) // ignore possible failures here
                  // write to stdout (not via uring)
                  _env.out.print(recover val consume buf end)
                  _ring.close() // shutdown the ring after all ops completed
                end

              be failed(op: URingOp) =>
                env.err.print("URing op failed")
            

Result

https://github.com/mfelsche/pony-uring

 

Focusses on File IO

Still very much incomplete

Thank You