Skip to content

pcall doesn't work in conjection with coroutines. #45

Description

@AmandaCameron

It seems that coroutines and pcalls don't quite work right when nested. I'm not sure why it is, but pcalls and coroutine don't play nice together.

This is a complete example program, that reproduces the problem. It should be spitting out several Tick: lines, instead it's spitting nothing out at all.

If you comment out the pcall stuff, it functions as expected.

package main

import (
    "fmt"

    "github.com/yuin/gopher-lua"
)

func main() {
    state := lua.NewState()
    state.OpenLibs()

    main, err := state.LoadString(threadLua)
    if err != nil {
        panic(err)
    }

    thread := state.NewThread()

    s, err, _ := state.Resume(thread, main)
    if err != nil {
        panic(err)
    }

    for i := 0; i < 20; i++ {
        if s != lua.ResumeError {
            s, err, _ = state.Resume(thread, main, lua.LString("tick"), lua.LNumber(i))

            if err != nil {
                fmt.Println("Error: ", err)

                break
            }
        }
    }
}

var threadLua = `
local ok, err = pcall(
  function()
    local evt, n = coroutine.yield()

        print("Tick: " .. n)

    if n > 10 then
      error("N is > 10")
    end
  end)

if not ok then
  print("Got Error: " .. err)
  error("Dying.")
end
`

Activity

  1. yuin commented on Aug 23, 2015

    @yuin
    Owner

    GopherLua is a Lua5.1 implementation.
    So codes that does not work in the original C-Lua 5.1 implementation does not work in the GopherLua too.

    It seems that your code does not work in the original C-Lua.

    #include <stdio.h>
    #include "lua.h"
    
    int main(int argc, char **argv) {
      lua_State *L;
      lua_State *co;
      int       i;
      int       status;
    
      L = (lua_State*)luaL_newstate();
      luaopen_base(L);
    
      co = lua_newthread(L);
      if (luaL_loadfile(co, "test.lua")) {
          printf("%s\n", lua_tostring(co, lua_gettop(co)));
          lua_close(co);
          return;
      }
    
      for(i = 0; i < 20; i++) {
        lua_pushstring(co, "tick");
        lua_pushinteger(co, i);
        status = lua_resume(co, 2);
        if(status == LUA_ERRRUN && lua_isstring(co, -1)) {
          printf("Error: %s",  lua_tostring(co, -1));
          lua_pop(co, 1);
          break;
        }
      }
    
      lua_close(L);
      return 0;
    }

    This code generates an error: "Got Error: attempt to yield across metamethod/C-call boundary".

  2. AmandaCameron commented on Aug 23, 2015

    @AmandaCameron
    Author

    That is a seperate issue in C-Lua that can be worked around with the following Lua code, however it's because the coroutine yield is trying to pass through the pcall stack, which can't be serialised in the C Lua stack as a sane value without a patch to the VM. I would note that this work-around doesn't work with Gopher-Lua.

    xpcall = function( _fn, _fnErrorHandler )
        local typeT = type( _fn )
        assert( typeT == "function", "bad argument #1 to xpcall (function expected, got "..typeT..")" )
        local co = coroutine.create( _fn )
        local tResults = { coroutine.resume( co ) }
        while coroutine.status( co ) ~= "dead" do
            tResults = { coroutine.resume( co, coroutine.yield() ) }
        end
        if tResults[1] == true then
            return true, unpack( tResults, 2 )
        else
            return false, _fnErrorHandler( tResults[2] )
        end
    end
    
    pcall = function( _fn, ... )
        local typeT = type( _fn )
        assert( typeT == "function", "bad argument #1 to pcall (function expected, got "..typeT..")" )
        local tArgs = { ... }
        return xpcall(
            function()
                return _fn( unpack( tArgs ) )
            end,
            function( _error )
                return _error
            end
        )
    end
  3. yuin commented on Aug 23, 2015

    @yuin
    Owner

    Hmmm, your work-around works for me( it seems the same result with C-Lua5.1.4).

    test.go

    package main
    
    import (
        "fmt"
    
        "github.com/yuin/gopher-lua"
    )
    
    func main() {
        state := lua.NewState()
    
        main, err := state.LoadFile("test.lua")
        if err != nil {
            panic(err)
        }
    
        thread := state.NewThread()
        s := lua.ResumeOK
        for i := 0; i < 20; i++ {
            if s != lua.ResumeError {
                s, err, _ = state.Resume(thread, main, lua.LString("tick"), lua.LNumber(i))
                if err != nil {
                    fmt.Println("Error: ", err)
    
                    break
                }
            }
        }
    }

    test.lua

    require("workaround")
    
    local ok, err = pcall(
      function()
        local evt, n = coroutine.yield()
    
            print("Tick: " .. n)
    
        if n > 10 then
          error("N is > 10")
        end
      end)
    
    if not ok then
      print("Got Error: " .. tostring(err))
      error("Dying.")
    end

    workaround.lua

    xpcall = function( _fn, _fnErrorHandler )
        local typeT = type( _fn )
        assert( typeT == "function", "bad argument #1 to xpcall (function expected, got "..typeT..")" )
        local co = coroutine.create( _fn )
        local tResults = { coroutine.resume( co ) }
        while coroutine.status( co ) ~= "dead" do
            tResults = { coroutine.resume( co, coroutine.yield() ) }
        end
        if tResults[1] == true then
            return true, unpack( tResults, 2 )
        else
            return false, _fnErrorHandler( tResults[2] )
        end
    end
    
    pcall = function( _fn, ... )
        local typeT = type( _fn )
        assert( typeT == "function", "bad argument #1 to pcall (function expected, got "..typeT..")" )
        local tArgs = { ... }
        return xpcall(
            function()
                return _fn( unpack( tArgs ) )
            end,
            function( _error )
                return _error
            end
        )
    end

    test.c

    #include <stdio.h>
    #include "lua.h"
    
    int main(int argc, char **argv) {
      lua_State *L;
      lua_State *co;
      int       i;
      int       status;
    
    
      L = (lua_State*)luaL_newstate();
      luaL_openlibs(L);
    
      co = lua_newthread(L);
      if (luaL_loadfile(co, "test.lua")) {
          printf("%s\n", lua_tostring(co, lua_gettop(co)));
          lua_close(co);
          return;
      }
    
      for(i = 0; i < 20; i++) {
        lua_pushstring(co, "tick");
        lua_pushinteger(co, i);
        status = lua_resume(co, 2);
        if(status == LUA_ERRRUN && lua_isstring(co, -1)) {
          printf("Error: %s",  lua_tostring(co, -1));
          lua_pop(co, 1);
          break;
        }
      }
    
      lua_close(L);
      return 0;
    }

    GopherLua(master) prints

    Tick: 1
    Error:  can not resume a dead thread

    C-Lua(5.1.4) prints

    Tick: 1
    Error: attempt to call a nil value
  4. AmandaCameron commented on Aug 23, 2015

    @AmandaCameron
    Author

    Hrm, it would seem I'm misunderstanding something in the stack then. I'll do some more digging tomorrow and comment if I can find anything.

  5. yuin commented on Sep 22, 2015

    @yuin
    Owner

    Due to lack of your response I am closing this issue for now.
    Please feel free to reopen this issue if you have any questions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions