coxpcall.lua 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. -------------------------------------------------------------------------------
  2. -- (Not needed for LuaJIT or Lua 5.2+)
  3. --
  4. -- Coroutine safe xpcall and pcall versions
  5. --
  6. -- https://keplerproject.github.io/coxpcall/
  7. --
  8. -- Encapsulates the protected calls with a coroutine based loop, so errors can
  9. -- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines
  10. -- yielding inside the call to pcall or xpcall.
  11. --
  12. -- Authors: Roberto Ierusalimschy and Andre Carregal
  13. -- Contributors: Thomas Harning Jr., Ignacio Burgueño, Fabio Mascarenhas
  14. --
  15. -- Copyright 2005 - Kepler Project
  16. --
  17. -- $Id: coxpcall.lua,v 1.13 2008/05/19 19:20:02 mascarenhas Exp $
  18. -------------------------------------------------------------------------------
  19. -------------------------------------------------------------------------------
  20. -- Checks if (x)pcall function is coroutine safe
  21. -------------------------------------------------------------------------------
  22. local function isCoroutineSafe(func)
  23. local co = coroutine.create(function()
  24. return func(coroutine.yield, function() end)
  25. end)
  26. coroutine.resume(co)
  27. return coroutine.resume(co)
  28. end
  29. -- No need to do anything if pcall and xpcall are already safe.
  30. if isCoroutineSafe(pcall) and isCoroutineSafe(xpcall) then
  31. copcall = pcall
  32. coxpcall = xpcall
  33. return { pcall = pcall, xpcall = xpcall, running = coroutine.running }
  34. end
  35. -------------------------------------------------------------------------------
  36. -- Implements xpcall with coroutines
  37. -------------------------------------------------------------------------------
  38. local performResume, handleReturnValue
  39. local oldpcall, oldxpcall = pcall, xpcall
  40. local pack = table.pack or function(...) return {n = select("#", ...), ...} end
  41. local unpack = table.unpack or unpack
  42. local running = coroutine.running
  43. local coromap = setmetatable({}, { __mode = "k" })
  44. function handleReturnValue(err, co, status, ...)
  45. if not status then
  46. return false, err(debug.traceback(co, (...)), ...)
  47. end
  48. if coroutine.status(co) == 'suspended' then
  49. return performResume(err, co, coroutine.yield(...))
  50. else
  51. return true, ...
  52. end
  53. end
  54. function performResume(err, co, ...)
  55. return handleReturnValue(err, co, coroutine.resume(co, ...))
  56. end
  57. local function id(trace, ...)
  58. return trace
  59. end
  60. function coxpcall(f, err, ...)
  61. local current = running()
  62. if not current then
  63. if err == id then
  64. return oldpcall(f, ...)
  65. else
  66. if select("#", ...) > 0 then
  67. local oldf, params = f, pack(...)
  68. f = function() return oldf(unpack(params, 1, params.n)) end
  69. end
  70. return oldxpcall(f, err)
  71. end
  72. else
  73. local res, co = oldpcall(coroutine.create, f)
  74. if not res then
  75. local newf = function(...) return f(...) end
  76. co = coroutine.create(newf)
  77. end
  78. coromap[co] = current
  79. return performResume(err, co, ...)
  80. end
  81. end
  82. local function corunning(coro)
  83. if coro ~= nil then
  84. assert(type(coro)=="thread", "Bad argument; expected thread, got: "..type(coro))
  85. else
  86. coro = running()
  87. end
  88. while coromap[coro] do
  89. coro = coromap[coro]
  90. end
  91. if coro == "mainthread" then return nil end
  92. return coro
  93. end
  94. -------------------------------------------------------------------------------
  95. -- Implements pcall with coroutines
  96. -------------------------------------------------------------------------------
  97. function copcall(f, ...)
  98. return coxpcall(f, id, ...)
  99. end
  100. return { pcall = copcall, xpcall = coxpcall, running = corunning }