-
Notifications
You must be signed in to change notification settings - Fork 1
/
pthread.lua
214 lines (196 loc) · 6.02 KB
/
pthread.lua
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
--
-- Copyright (C) 2023 Masatoshi Fukunaga
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
--
--- assign to local
local select = select
local tostring = tostring
local tonumber = tonumber
local load = load
local loadstring = loadstring
local open = io.open
local getinfo = debug.getinfo
local sub = string.sub
local format = string.format
local dump = string.dump
local match = string.match
local concat = table.concat
local unpack = unpack or table.unpack
local poll_wait_readable = require('gpoll').wait_readable
local new_thread = require('pthread.thread').new
local instanceof = require('metamodule').instanceof
local EINVAL = require('errno').EINVAL
--- define pthread.thread metatable
--- @class pthread.thread
--- @field join fun(self: pthread.thread):(ok:boolean, err:any, again:boolean)
--- @field cancel fun(self: pthread.thread, notify: boolean?):(ok:boolean, err:any)
--- @field status fun(self: pthread.thread):(status:string, errmsg:string)
--- @field fd fun(self: pthread.thread):integer
--- @class pthread
--- @field private thread pthread.thread
local Pthread = {}
--- init
--- @param newfn fun(src:string, ...:pthread.thread.queue):(pthread.thread?, any, boolean?)
--- @param src string
--- @param ... pthread.channel
--- @return pthread? self
--- @return any err
--- @return boolean? again
function Pthread:init(newfn, src, ...)
--- @type pthread.channel[]|pthread.thread.queue[]
local qs = {
...,
}
for i = 1, select('#', ...) do
local ch = qs[i]
if not instanceof(ch, 'pthread.channel') then
error('invalid argument #' .. i + 1 ..
': expected pthread.channel, got ' .. tostring(ch))
end
qs[i] = ch.queue
end
local thread, err, again = newfn(src, unpack(qs))
if not thread then
return nil, err, again
end
self.thread = thread
return self
end
--- join
--- @param sec number?
--- @return boolean ok
--- @return any err
--- @return boolean? timeout
function Pthread:join(sec)
local ok, err, again = self.thread:join()
if again then
-- wait until the thread terminates
ok, err, again = poll_wait_readable(self.thread:fd(), sec)
if not ok then
return false, err, again
end
ok, err = self.thread:join()
end
return ok, err
end
--- cancel
--- @param notify boolean?
--- @return boolean ok
--- @return any err
function Pthread:cancel(notify)
return self.thread:cancel(notify)
end
--- status
--- @return string status
--- @return string errmsg
function Pthread:status()
return self.thread:status()
end
Pthread = require('metamodule').new(Pthread)
local LUA_VERSION = tonumber(match(_VERSION, 'Lua (.+)$'))
local LOADFN = LUA_VERSION <= 5.1 and loadstring or load
local STARTFN = {
[[
-- wrap pthread.queue arguments in pthread.channel
local unpack = unpack or table.unpack
_G.PTHREAD_ARG = {
self = ...,
channel = {}
}
do
local wrap_channel = require('pthread.channel').wrap
local channel = _G.PTHREAD_ARG.channel
local queues = {select(2, ...)}
for i = 1, #queues do
channel[i] = wrap_channel(queues[i])
end
end
]],
'-- load and run user defined function',
'fn = assert(' .. (LUA_VERSION <= 5.1 and 'loadstring' or 'load') .. '(',
'',
'))',
'fn(_G.PTHREAD_ARG.self, unpack(_G.PTHREAD_ARG.channel))',
}
--- new
--- @param str string
--- @param ... pthread.channel
--- @return pthread? self
--- @return any err
--- @return boolean? again
local function new(str, ...)
-- evaluate the given string as a lua script
local fn, err = LOADFN(str)
if not fn then
return nil, EINVAL:new(err)
end
-- insert the given string into STARTFN
STARTFN[4] = format('%q', str)
local src = concat(STARTFN, '\n')
return Pthread(new_thread, src, ...)
end
--- new_with_func
--- @param fn fun(pthread.self, ...:pthread.channel)
--- @param ... pthread.channel
local function new_with_func(fn, ...)
local str = dump(fn)
return new(str, ...)
end
--- new_with_file
--- @param filename string
--- @param ... pthread.channel
--- @return pthread? self
--- @return any err
--- @return boolean? again
local function new_with_file(filename, ...)
local file, err = open(filename, 'r')
if not file then
return nil, EINVAL:new(err)
end
local str
str, err = file:read('*a')
file:close()
if not str then
return nil, err
end
return new(str, ...)
end
--- clone
--- @param basedir string?
--- @param ... pthread.channel
--- @return pthread? self
--- @return any err
--- @return boolean? again
local function clone(basedir, ...)
assert(basedir == nil or type(basedir) == 'string',
'basedir must be string or nil')
local filename = sub(getinfo(2, 'S').source, 2)
if basedir and sub(filename, 1, 1) ~= '/' then
-- prepend basedir to relative path
filename = basedir .. '/' .. filename
end
return new_with_file(filename, ...)
end
return {
new = new,
new_with_func = new_with_func,
new_with_file = new_with_file,
clone = clone,
}