coxpcall.lua 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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. _G.copcall = pcall
  32. _G.coxpcall = xpcall
  33. return { pcall = pcall, xpcall = xpcall, running = coroutine.running }
  34. end
  35. -------------------------------------------------------------------------------
  36. -- Implements xpcall with coroutines
  37. -------------------------------------------------------------------------------
  38. ---@diagnostic disable-next-line
  39. local performResume
  40. local oldpcall, oldxpcall = pcall, xpcall
  41. local pack = table.pack or function(...) return {n = select("#", ...), ...} end
  42. local unpack = table.unpack or unpack
  43. local running = coroutine.running
  44. --- @type table<thread,thread>
  45. local coromap = setmetatable({}, { __mode = "k" })
  46. local function handleReturnValue(err, co, status, ...)
  47. if not status then
  48. return false, err(debug.traceback(co, (...)), ...)
  49. end
  50. if coroutine.status(co) == 'suspended' then
  51. return performResume(err, co, coroutine.yield(...))
  52. else
  53. return true, ...
  54. end
  55. end
  56. function performResume(err, co, ...)
  57. return handleReturnValue(err, co, coroutine.resume(co, ...))
  58. end
  59. --- @diagnostic disable-next-line: unused-vararg
  60. local function id(trace, ...)
  61. return trace
  62. end
  63. function _G.coxpcall(f, err, ...)
  64. local current = running()
  65. if not current then
  66. if err == id then
  67. return oldpcall(f, ...)
  68. else
  69. if select("#", ...) > 0 then
  70. local oldf, params = f, pack(...)
  71. f = function() return oldf(unpack(params, 1, params.n)) end
  72. end
  73. return oldxpcall(f, err)
  74. end
  75. else
  76. local res, co = oldpcall(coroutine.create, f)
  77. if not res then
  78. local newf = function(...) return f(...) end
  79. co = coroutine.create(newf)
  80. end
  81. coromap[co] = current
  82. return performResume(err, co, ...)
  83. end
  84. end
  85. --- @param coro? thread
  86. local function corunning(coro)
  87. if coro ~= nil then
  88. assert(type(coro)=="thread", "Bad argument; expected thread, got: "..type(coro))
  89. else
  90. coro = running()
  91. end
  92. while coromap[coro] do
  93. coro = coromap[coro]
  94. end
  95. if coro == "mainthread" then return nil end
  96. return coro
  97. end
  98. -------------------------------------------------------------------------------
  99. -- Implements pcall with coroutines
  100. -------------------------------------------------------------------------------
  101. function _G.copcall(f, ...)
  102. return coxpcall(f, id, ...)
  103. end
  104. return { pcall = copcall, xpcall = coxpcall, running = corunning }