coxpcall.lua 3.6 KB

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