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