Cosock

Cosock is a coroutine runtime written in pure Lua and based on the popular luasocket library.

The goal of the project is to provide the same interfaces that luasocket provides but wrapped up in coroutines to allow for concurrent IO.

Note: these docs will use the term coroutine, task, and thread interchangeably to all mean a lua coroutine

For example, the following 2 lua programs use luasocket to define a tcp client and server.

--client.lua
local socket = require "socket"
local client = socket.tcp()
client:connect("0.0.0.0", 9999)
while true do
  print("sending ping")
  client:send("ping\n")
  local response = assert(client:receive())
  assert(response == "pong")
end
--server.lua
local socket = require "socket"
local server = socket.tcp()
server:bind("0.0.0.0", 9999)
server:listen()
print("listening", server:getsockname())
local client = server:accept()
while true do    
  local request = assert(client:receive())
  assert(request == "ping")
  print("sending pong")
  client:send("pong\n")
end

If you were to run lua ./server.lua first and then run lua ./client.lua you should see each terminal print out their "sending ..." messages forever.

Using cosock, we can actually write the same thing as a single application.

-- client_server.lua
local cosock = require "cosock"
local socket = require "cosock.socket"
local ip = "0.0.0.0"
local server = socket.tcp()
--- Since the client and server are in the same application
--- we can use an OS assigned port and share it across the
--- two tasks, to coordinate the two tasks to start in the order
--- we want, we can use a cosock channel to make sure both tasks
--- have the same port number
local port_tx, port_rx = cosock.channel.new()

--- Spawn a task for handling the server side of the socket
cosock.spawn(function()
  server:bind(ip, 0)
  local _ip, p = server:getsockname()
  port_tx:send(p)
  server:listen()
  local client = server:accept()
  while true do
    local request = assert(client:receive())
    print(string.format("received %q", request))
    assert(request == "ping")
    print("sending pong")
    client:send("pong\n")
  end
end, "server task")

--- Spawn a task for handling the client side of the socket
cosock.spawn(function()
  --- wait for the server to be ready.
  local port = assert(port_rx:receive())
  local client = socket.tcp()
  client:connect(ip, port)
  while true do    
    print("sending ping")
    client:send("ping\n")
    local request = assert(client:receive())
    assert(request == "pong")
  end
end, "client task")

--- Finally we tell cosock to run our 2 coroutines until they are done
--- which should be forever
cosock.run()

Now if we run this with lua ./client_server.lua we should see the messages alternate.

Notice that we called cosock.spawn twice, once for the server task and once for the client task, we are going to dig into that next. We also added a call to cosock.run at the bottom of our example, this function will run our tasks until there is no more work to do so it is important you don't forget it or nothing will happen.