probablycorey/wax — Issue #47: Synchronous calls to wax.http.request
Author: makeitnew
Created: 2012-01-23 23:16:40+00:00
State: open

There is a flaw in the wax http library which manifests itself when wax.http.request is called synchronously. This note describes the issue and proposes a workaround.

Let's say the user takes some action which causes wax.http.request to be called synchronously. But before the first call to request has terminated, the user takes some other action that causes wax.http.request to be called again. In this situation, one of the calls can return spurious results. This is because the lua_State and stack is getting clobbered in between calls. Here's how:

wax.http.request does the following in synchronous mode: 1) creates and starts an http connection; 2) saves the lua_State object in the connection object; 3) starts the operation; and 4) spins in a loop waiting for the connection to complete, relinquishing time to the run loop on each iteration. When the http response arrives a completion handler in the connection object is called. This handler uses the saved Lua state (L) to push results on the argument stack of wax.http.request so they can be passed back to the caller.

Now a problem occurs when an external event causes the http.request to be called again while it has relinquished time on the run loop. The Lua state has been stashed away in the first connection object. It is in an indeterminate state, i.e., in the middle of a function call. When http.request is called again, wax uses this same state for the new procedure call, clobbering the state in the first connection. Wax assumes a single-threaded non-reentrant environment where at most one procedure invocation stack is active at a time. This allows wax to use a single Lua state for all invocations. But stashing away the Lua call state while in the midst of a function call in the connection breaks this assumption.

This suggests that we want an independent Lua state for each call to wax.http.request. The easiest way to do this is to create a coroutine and to issue the request on the coroutine. Here's a simple wrapper that does the job:

```lua
function http_sync_wrapper(arg)

-- create a coroutine and call wax.http.request
local co = coroutine.create(
function()
local body, response = wax.http.request(arg)
coroutine.yield(body, response)
end
)

-- start the coroutine, wait for wax.http.request to complete
local status, body, response = coroutine.resume(co)

-- let coroutine terminate
coroutine.resume(co)

-- return to caller
return body, response
end
```
The operation of the above function is simple. It calls http.request in the context of a new coroutine. This creates an independent Lua state that can be saved in the connection object. When the request completes the coroutine yields back to its caller. A reentrant synchronous call to wax.http.request will not interfere with other ongoing calls to the same function.

There are ways to fix the problem in the objective-c code but I wanted to avoid touching the low-level library if possible. Probably the best place to put this wrapper is in http.lua.

Note that while the existing design isn't necessarily a problem for asynchronous calls, saving the state of Lua stack frame after it has returned is perhaps not the best idea either. So it might be worthwhile to wrap all calls to wax.http.request--synchronous and asynchronous--in a coroutine.