Luring is a callback-style interface for Lua to "io_uring" which is the asynchronous I/O framework introduced in Linux Kernel 5.1.
Luring uses liburing internally, you sould have installed liburing to compile it.
local luring = require "luring"
Luring deal with file descriptors. You could use libraries like "luaposix" to get descriptors, which luring could deal with. Besides, luring provide a simple helper open_file(file_path, mode)
to open regular file.
To use luring, you should have knownledge about how io_uring works, here're some resources:
- Ringing in a new asynchronous I/O API By Jonathan Corbet - LWN.net
- An Introduction to the io_uring Asynchronous I/O Framework By Bijan Mottahedeh - Oracle Linux Blog
In short, it is just 4 step to correctly handle I/O actions by luring:
- Do actions by functions provided by luring, such as
write(ring, fd, content, offest, callback)
,read(ring, fd, size, offest, callback)
,recv(ring, sockfd, size, flags, callback)
,send(ring, sockfd, content, flags, callback)
,accept(ring, sockfd, flags, callback)
- Submit.
submit(ring)
orsubmit_and_wait(ring, wait_nr)
. - Handle CQEs. This step you use
do_cqes(ring)
, which will block the thread and call the callbacks until no CQEs leaves in ring. - Free resources. The first argument of callbacks is a userdata, which is a pointer to CQE. You MUST call
cqe_seen(ring, cqe)
on the userdata in the end of your callback, or the callback will be called repeatly and the other CQEs could not be processed.
Apache License, version 2.0
https://www.apache.org/licenses/LICENSE-2.0
local luring = require "luring"
Open file and return the file descriptor. If mode
is not presented, it's w
.
mode
accepts one of these options:
w
: open the file in read/write mode, create the file if it does not existsw+
: same asw
, but it will clear original content of the filea
: open the file in write only mode, create the file if it does not existsa+
: same asa
, but it will clear original content of the filer
: open the file in read only mode
The created file's permission will be -rw-rw-r--.
.
It will return -errno
when fails.
Return the explain of the errno
.
Create a new io_uring instance and return the pointer. It is always refered as ring
later.
entries
is the size of the ring, flags
should be 0
for now. The userdata is set to automatically call queue_exit
when is being collected as garbage.
Free the ring
.
Submit the SQEs you just changed. Note that it will make a system call.
Submit the SQEs and wait until wait_nr
CQEs filled in queue (after these SQEs' work done). Note that it will make a system call.
Do callbacks of finished works.
This function MUST be called in the end of your callbacks. You can treat this function as that it marks the cqe
is usable for next result.
Beside io_uring_cqe_seen
, this function also unrefer the callback and free the memory of the buffer. The result
you got will not be freed, because it's a copy of the result (Lua always do copy when using lua_pushlstring
).
These actions' effect is closed to system call with same name. They are accept a function as a callback, the first argument of the callback is always the CQE (userdata), the second is always the result code. Genernally the negative result code means error, and you may get the exact error number from their negative (-result_code
).
If the io_uring queue is full, nil
will be returned, otherwise a userdata of the SQE will be returned.
Luring will do malloc
for every needed actions (read
, recv
and accept
. They need buffer to store data.). It might costs. You could use custom allocator to override the standard manual memory management functions, for example "mimalloc".
luring.write(ring: userdata, fd: integer, content: string, offest: integer, callback: (function(cqe: userdata, res: integer): nil)?): userdata
Write content
to fd
depends on offest
.
luring.read(ring: userdata, fd: integer, size: integer, offest: integer, callback: (function(cqe: userdata, res: integer, result: string): nil)?): userdata
Read a chunk with size
max size from fd
depends on offest
. result
is the result.
luring.recv(ring: userdata, sockfd: integer, size: integer, flags: integer, callback: (function(cqe: userdata, res: integer, result: string): nil)?): userdata
Recvive data from sockfd
with size
max size. result
is the result.
luring.send(ring: userdata, sockfd: integer, content: string, flags: integer, callback: (function(cqe: userdata, res: integer): nil)?): userdata
luring.accept(ring: userdata, sockfd: integer, flags: integer, callback: (function(cqe: userdata, res: integer): nil)?): userdata
The res
is the file descriptor of the socket of the accepted connection if it does not fail.