Now that I have a proper keyboard, let me explain a little better.
I assume that since you're using luv and a write method, you're talking about the uv.write function in luv. This is non-blocking and coroutines have nothing to do with it. If you do s:write("a") and s:write("b") in a coroutine, it won't yield for either one, but rather will put both strings on the socket's write queue. The coroutine will then move on and finish without waiting for the data to actually write and flush. Then your second coroutine will run and do the same thing.
So actually, the first case is guaranteed to output "abab" and not "aabb" since there is nothing to cause the first coroutine to yield. The two coroutines never run concurrently, just one and then the other.
Now if you were using some coroutine aware stream like the write function in the coro-net package on lit things would be different. As you're guessed, the first write would cause the coroutine to yield and not resume till the data has been flushed. Then the second coroutine would run and do the same thing. Since the data goes in a fifo queue, the first coroutine would most likely unblock first. Assuming that your writes are large enough to cause libuv to wait for each write to flush your output will be "aabb" since the two coroutines are running concurrently with cooperative multithreading.
A common pattern I use to wait for callback based APIs is as follows:
local function write(socket, data)
local thread = coroutine.running()
socket:write(data, function (err)
assert(coroutine.yield(thread, not err, err))
end)
return coroutine.yield()
end