Luasocket and Coroutines?

Posted by Rivius on Thu 15 Sep 2011 10:57 PM — 5 posts, 26,672 views.

#0
I've never had the chance to use coroutines in lua, and as an amateur novice programmer, I'm not entirely sure of how they work but generally have a vague idea of the advantages from a brief talk with a friend. I've been attempting to use lua socket to pull information from a website for the purposes of finding out what organization a player is in and group them into it. The code I came up with does what is intended and works as follows:

function getorg(player)
	local w = assert(http.request("http://www.ironrealms.com/game/honors/Lusternia/"..player))
	
	if not w then
		ui.alert("There has been an error retrieving information from the website.")
		if IsConnected() then SendNoEcho("") end
		return
	end
	
	local m = core.match("(?:City|Commune)\: ([A-Z][a-z]+)", w)
	
	if not m then
		ui.message("This person appears to either be a rogue, or not exist at all.")
		if IsConnected() then SendNoEcho("") end
		return
	end
	
	add_name(player, m[1])
	if IsConnected() then SendNoEcho("") end
end


The thing about this though, is that it hangs while getting the information. I discussed this with a friend, and he mentioned that this may be exacerbated if the host was experiencing problems. He mentioned that to remove the hang, I would have to use a non-blocking method and utilize coroutines. I've tried a few things, but they did not seem to work at all. I've done searches and fail to really understand the code presented.

How would I take advantage of coroutines to speed this up?

I would show my previous attempts at using connect and receive, but I scrapped them out of frustration.
#1

function whois(player, html)
	local w = html
	
	if not w then
		ui.alert("There has been an error retrieving information from the website.")
		if IsConnected() then SendNoEcho("") end
		return
	end
	
	local m = core.match("(?:City|Commune)\: ([A-Z][a-z]+)", w)
	
	if not m then
		ui.message("This person appears to either be a rogue, or not exist at all.")
		if IsConnected() then SendNoEcho("") end
		return
	end
	
	add_name(player, m[1])
	if IsConnected() then SendNoEcho("") end
end

function download_page(player)
	local h = "www.ironrealms.com"
	local c = assert(socket.connect(h, 80))
	c:send("GET http://www.ironrealms.com/game/honors/Lusternia/"..player.." HTTP/1.0\r\n\r\n")
	local x = coroutine.create(
		function ()
			whois(player, receive(c))
		end)
		coroutine.resume(x)
end

function receive(c)
	local status = "timeout"
	local msg, a
	
	c:settimeout(0)
	while (status == "timeout") or (status==nil and a~=nil) do
		a, status, d = c:receive()
		if a then msg = msg..a end
		if d then msg = msg..d end
		if status == "timeout" then
			coroutine.yield(true)
		end
	end
	c:close()
	return msg
end


Here's my lastest attempt to get it to work, but it just does nothing when I run the download function.
Australia Forum Administrator #2
Luasocket itself supports non-blocking calls, because I wrote a small MUD server using it. However I'm not sure that the HTTP interface makes use of that.

I don't think coroutines will directly help, because the underlying call still blocks.

I'm not sure this helps:


c:settimeout(0)


That effectively means "no timeout" doesn't it? (that is, blocking)

Basically for non-blocking to work you need a different design. That is, initially you send the request to the web server. That can (hopefully) immediately exit (although DNS lookups might slow it down quite a bit).

Then you need to periodically check (eg. every second) to see if the data has arrived. There is no way to have a non-blocking, but atomic, function call that will get something like whois info.

#3
I swear I've seen the settimeout(0) work in the past, allowing the while loop to go at full speed, yielding as needed. Now, it requires a value of at least 1 for the timeout, and the function still seems to run in the foreground. Obviously, we don't really get the proper use of coroutines to do asynchronous things, like reading from a socket...
#4
Rivius can you paste your code for your IsConnected() function. I'd be interested to see it to see if I could leverage it for a little luasocket script I'm working on.

The


c:settimeout(0)


helped a lot, thnx!

Hm, based upon your suggestion, do you think creating a timer with a 1 second interval within MUSH and periodically reading the socket via c:receive() with a c:settimeout(0) is the best way to go then? It seems that's the strategy if you want MUSH to be responsive and still be able to check for messages from any TCP server you're connected to.