One problem is that redis-lua defines some iterators as coroutines, but these iterators execute Network operations that can be generated.
Therefore, coroutine.yield is used for two very different things: for iterators and copas. When network calls are nested in iterators, the network output is The coroutine.wrap of the iterator is intercepted, not by copas.
The following example shows the problem:
local function iterator ()
for i = 1, 2 do
if i == 2 then coroutine.yield () end - network yield
coroutine.yield () - iterator yield
end
end
local citerator = coroutine.wrap (iterator)
local function loop () - use of the iterator within a copas thread
while citerator () do end
end
local cloop = coroutine.create (loop)
while coroutine.resume (cloop) do end - same as copas loop, executes the cloop thread
Is there a “standard” solution to this problem that still allows iteration with coroutines?
I was able to make a small example by “marking” the output (see below), but it is not compatible with the existing code. I can not modify the copas code, but must update the iterator in redis-lua .
local function wrap (f, my_tag)
- same as coroutine.wrap, but uses my_tag to yield again
local co = coroutine. create (f)
return function ()
local t = table.pack (coroutine.resume (co))
local code = t [1]
local tag = t [ 2]
table.remove (t, 1)
table.remove (t, 1)
if tag == nil then
return
elseif my_tag == tag then
return table.unpack (t)
else
coroutine.yield (tag, table.unpack (t))
end
end
end< br />
local Iterator = {} - tag for iterator yields
local Network = {} - tag for network yields
local function iterator ()
for i = 1, 2 do
if i == 2 then coroutine.yield (Network, i) end
coroutine.yield (Iterator, i)
end
end
local citerator = wrap (iterator, Iterator)
local function loop ()
while citerator () do end
end
local cloop = wrap (loop, Network)
while cloop () do end
Is there a better solution?
The idea of using tag values to distinguish the iterator output from the rest is good. You just need to make sure that all The yield of the non-iterator function is passed to the coroutine level, including any parameters/return values of coroutine.yield and coroutine.resume (the latter is implicit when calling the coroutine.wraped function).
More specifically, if you have this code in redis-lua:
-- ...
return coroutine.wrap( function()
- ...
while true do
- ...
coroutine.yield( some_values )
end
end )
You change it to:
-- ...
local co_func = coroutine.wrap( function()
- ...< br /> while true do
- ...
coroutine.yield( ITERATOR_TAG, some_values) - mark all iterator yields
end
return ITERATOR_TAG - returns are also intended for the iterator
end )
return function()
return pass_yields( co_func, co_func()) - initial resume of the iterator
end
ITERATOR_TAG and pass_yields functions are near the top of redis.lua:< /p>
local ITERATOR_TAG = {} - unique value to mark yields/returns
local function pass_yields( co_func, ... )
if ... == ITERATOR_TAG then - yield (or return) intended for iterator?
return select( 2, ...) - strip the ITERATOR_TAG from results and return
else
- -pass other yields/resumes back and forth until we hit another iterator
- yield (or return); using tail recursion here instead of a loop makes
- handling vararg lists easier.
return pass_yields( co_func, co_func( coroutine.yield( ...)))
end
end
AFAIK, redis-lua developers plan to mark another version before the end of this year , So they may be grateful for the pull request.
I tried to use the redis-lua library inside copas. It needs some fixes.
One problem is redis-lua defines some iterators as coroutines, but these iterators perform network operations that can be generated.
So, coroutine.yield is used for two very different things: for iterators And copas. When the network is called When nested in an iterator, the network output is intercepted by the iterator’s coroutine.wrap, not by copas.
The following example shows the problem:
< pre>local function iterator ()
for i = 1, 2 do
if i == 2 then coroutine.yield () end – network yield
coroutine.yield () – iterator yield
end
end
local citerator = coroutine.wrap (iterator)
local function loop () – use of the iterator within a copas thread
while citerator () do end
end
local cloop = coroutine.create (loop)
while coroutine.resume (cloop) do end – same as copas loop, executes the cloop thread
Is there a “standard” solution to this problem that still allows the use of coroutines for iteration?
I was able to make a small example by “marking” the output (see below), but it is not compatible with the existing code. I can not modify the copas code, but must update the iterator in redis-lua .
local function wrap (f, my_tag)
- same as coroutine.wrap, but uses my_tag to yield again
local co = coroutine. create (f)
return function ()
local t = table.pack (coroutine.resume (co))
local code = t [1]
local tag = t [ 2]
table.remove (t, 1)
table.remove (t, 1)
if tag == nil then
return
elseif my_tag == tag then
return table.unpack (t)
else
coroutine.yield (tag, table.unpack (t))
end
end
end< br />
local Iterator = {} - tag for iterator yields
local Network = {} - tag for network yields
local function iterator ()
for i = 1, 2 do
if i == 2 then coroutine.yield (Network, i) end
coroutine.yield (Iterator, i)
end
end
local citerator = wrap (iterator, Iterator)
local fu nction loop ()
while citerator () do end
end
local cloop = wrap (loop, Network)
while cloop () do end
Is there a better solution?
Lua coroutine always succumbs to the last thread they resume. Copas socket functions expect to return to the Copas event loop, but they will be used for implementation The coroutine of the redis-lua iterator is stuck. Unfortunately, you can’t solve this problem except for changing the code of the redis-lua iterator. The reason no one did this is until Lua 5.2 (LuaJIT can also do the same ), it’s not even possible to generate from iterator functions (iterator generation in redis-lua works fine, because they never leave the iterator function, but you can’t yield beyond the for loop, just like the Copas socket function would try Do it).
The idea of using the tag value to distinguish the iterator output from the rest is good. You just need to make sure to pass the yield of all non-iterator functions to the coroutine level, Include any parameters/return values of coroutine.yield and coroutine.resume (the latter is implicit when calling the coroutine.wraped function).
More specifically, if you are in redis-lua There is this code:
-- ...
return coroutine.wrap( function()
- ...
while true do
- ...
coroutine.yield( some_values )
end
end )
You change it to:
< p>
-- ...
local co_func = coroutine.wrap( function()
- ...
while true do
- ...
coroutine.yield( ITERATOR_TAG, some_values) - mark all iterator yields
end
return ITERATOR_TAG - returns are also intended for the iterator
end )
return function()
return pass_yields( co_func, c o_func()) - initial resume of the iterator
end
The ITERATOR_TAG and pass_yields functions are near the top of redis.lua:
local ITERATOR_TAG = {} - unique value to mark yields/returns
local function pass_yields( co_func, ... )
if ... == ITERATOR_TAG then - yield (or return) intended for iterator?
return select( 2, ...) - strip the ITERATOR_TAG from results and return
else
- pass other yields/resumes back and forth until we hit another iterator< br /> - yield (or return); using tail recursion here instead of a loop makes
- handling vararg lists easier.
return pass_yields( co_func, co_func( coroutine.yield( ...) ))
end
end
AFAIK, redis-lua developers plan to mark another version before the end of this year, so they might be grateful for the pull request. p>