Skip to content

Commit afd2dbc

Browse files
authored
Merge pull request #40 from scr-oath/plugin-with-args
2 parents 2844fe2 + 75f263a commit afd2dbc

File tree

7 files changed

+412
-150
lines changed

7 files changed

+412
-150
lines changed

plugin/api.go

Lines changed: 112 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,24 @@ package plugin
44
import (
55
"context"
66
"sync"
7-
8-
"github.com/vadv/gopher-lua-libs/stats"
9-
10-
cloudwatch "github.com/vadv/gopher-lua-libs/aws/cloudwatch"
11-
chef "github.com/vadv/gopher-lua-libs/chef"
12-
cmd "github.com/vadv/gopher-lua-libs/cmd"
13-
crypto "github.com/vadv/gopher-lua-libs/crypto"
14-
db "github.com/vadv/gopher-lua-libs/db"
15-
filepath "github.com/vadv/gopher-lua-libs/filepath"
16-
goos "github.com/vadv/gopher-lua-libs/goos"
17-
http "github.com/vadv/gopher-lua-libs/http"
18-
humanize "github.com/vadv/gopher-lua-libs/humanize"
19-
inspect "github.com/vadv/gopher-lua-libs/inspect"
20-
ioutil "github.com/vadv/gopher-lua-libs/ioutil"
21-
json "github.com/vadv/gopher-lua-libs/json"
22-
log "github.com/vadv/gopher-lua-libs/log"
23-
pb "github.com/vadv/gopher-lua-libs/pb"
24-
prometheus "github.com/vadv/gopher-lua-libs/prometheus/client"
25-
regexp "github.com/vadv/gopher-lua-libs/regexp"
26-
storage "github.com/vadv/gopher-lua-libs/storage"
27-
strings "github.com/vadv/gopher-lua-libs/strings"
28-
tac "github.com/vadv/gopher-lua-libs/tac"
29-
tcp "github.com/vadv/gopher-lua-libs/tcp"
30-
telegram "github.com/vadv/gopher-lua-libs/telegram"
31-
template "github.com/vadv/gopher-lua-libs/template"
32-
time "github.com/vadv/gopher-lua-libs/time"
33-
xmlpath "github.com/vadv/gopher-lua-libs/xmlpath"
34-
yaml "github.com/vadv/gopher-lua-libs/yaml"
35-
zabbix "github.com/vadv/gopher-lua-libs/zabbix"
7+
"time"
368

379
lua "github.com/yuin/gopher-lua"
3810
)
3911

4012
type luaPlugin struct {
4113
sync.Mutex
14+
*sync.Cond
4215
state *lua.LState
4316
cancelFunc context.CancelFunc
4417
running bool
4518
error error
4619
body *string
4720
filename *string
4821
jobPayload *string
22+
args []lua.LValue
23+
doneCh lua.LChannel
24+
started bool
4925
}
5026

5127
func (p *luaPlugin) getError() error {
@@ -77,51 +53,38 @@ func (p *luaPlugin) setRunning(val bool) {
7753
// NewPluginState return lua state
7854
func NewPluginState() *lua.LState {
7955
state := lua.NewState()
80-
// preload all
81-
filepath.Preload(state)
82-
http.Preload(state)
83-
inspect.Preload(state)
84-
ioutil.Preload(state)
85-
json.Preload(state)
86-
regexp.Preload(state)
87-
strings.Preload(state)
88-
tac.Preload(state)
89-
tcp.Preload(state)
90-
time.Preload(state)
91-
xmlpath.Preload(state)
92-
yaml.Preload(state)
93-
zabbix.Preload(state)
94-
telegram.Preload(state)
95-
storage.Preload(state)
96-
crypto.Preload(state)
97-
goos.Preload(state)
98-
humanize.Preload(state)
99-
db.Preload(state)
100-
chef.Preload(state)
101-
cmd.Preload(state)
102-
template.Preload(state)
103-
cloudwatch.Preload(state)
104-
log.Preload(state)
105-
prometheus.Preload(state)
106-
pb.Preload(state)
107-
stats.Preload(state)
56+
PreloadAll(state)
10857
return state
10958
}
11059

11160
func (p *luaPlugin) start() {
11261
p.Lock()
11362
state := NewPluginState()
63+
defer state.Close()
11464
p.state = state
11565
p.error = nil
11666
p.running = true
117-
isBody := (p.filename == nil)
118-
if !(p.jobPayload == nil) {
67+
p.doneCh = make(lua.LChannel, 1)
68+
p.started = true
69+
defer close(p.doneCh)
70+
isBody := p.filename == nil
71+
if p.jobPayload != nil {
11972
payload := *p.jobPayload
12073
state.SetGlobal(`payload`, lua.LString(payload))
12174
}
12275
ctx, cancelFunc := context.WithCancel(context.Background())
12376
p.cancelFunc = cancelFunc
12477
p.state.SetContext(ctx)
78+
newArg := state.NewTable()
79+
for _, arg := range p.args {
80+
switch t := arg.Type(); t {
81+
case lua.LTFunction:
82+
arg = state.NewFunctionFromProto(arg.(*lua.LFunction).Proto)
83+
}
84+
newArg.Append(arg)
85+
}
86+
state.SetGlobal(`arg`, newArg)
87+
p.Signal()
12588
p.Unlock()
12689

12790
// blocking
@@ -144,10 +107,38 @@ func checkPlugin(L *lua.LState, n int) *luaPlugin {
144107
return nil
145108
}
146109

110+
func NewLuaPlugin(L *lua.LState, n int) *luaPlugin {
111+
ret := &luaPlugin{}
112+
ret.Cond = sync.NewCond(&ret.Mutex)
113+
top := L.GetTop()
114+
for i := n; i <= top; i++ {
115+
arg := L.Get(i)
116+
switch t := arg.Type(); t {
117+
case lua.LTFunction:
118+
f := arg.(*lua.LFunction)
119+
if len(f.Upvalues) > 0 {
120+
L.ArgError(i, "cannot pass closures")
121+
}
122+
ret.args = append(ret.args, arg)
123+
case lua.LTTable:
124+
if L.GetMetatable(arg) != lua.LNil {
125+
L.ArgError(i, "tables with metadata are not allowed")
126+
}
127+
ret.args = append(ret.args, arg)
128+
case lua.LTNil, lua.LTBool, lua.LTNumber, lua.LTString, lua.LTChannel:
129+
ret.args = append(ret.args, arg)
130+
default:
131+
L.ArgError(i, t.String()+" is not allowed")
132+
}
133+
}
134+
return ret
135+
}
136+
147137
// DoString lua plugin.do_string(body) returns plugin_ud
148138
func DoString(L *lua.LState) int {
149139
body := L.CheckString(1)
150-
p := &luaPlugin{body: &body}
140+
p := NewLuaPlugin(L, 2)
141+
p.body = &body
151142
ud := L.NewUserData()
152143
ud.Value = p
153144
L.SetMetatable(ud, L.GetTypeMetatable(`plugin_ud`))
@@ -158,7 +149,8 @@ func DoString(L *lua.LState) int {
158149
// DoFile lua plugin.do_file(filename) returns plugin_ud
159150
func DoFile(L *lua.LState) int {
160151
filename := L.CheckString(1)
161-
p := &luaPlugin{filename: &filename}
152+
p := NewLuaPlugin(L, 2)
153+
p.filename = &filename
162154
ud := L.NewUserData()
163155
ud.Value = p
164156
L.SetMetatable(ud, L.GetTypeMetatable(`plugin_ud`))
@@ -170,7 +162,9 @@ func DoFile(L *lua.LState) int {
170162
func DoFileWithPayload(L *lua.LState) int {
171163
filename := L.CheckString(1)
172164
payload := L.CheckString(2)
173-
p := &luaPlugin{filename: &filename, jobPayload: &payload}
165+
p := NewLuaPlugin(L, 2)
166+
p.filename = &filename
167+
p.jobPayload = &payload
174168
ud := L.NewUserData()
175169
ud.Value = p
176170
L.SetMetatable(ud, L.GetTypeMetatable(`plugin_ud`))
@@ -182,7 +176,9 @@ func DoFileWithPayload(L *lua.LState) int {
182176
func DoStringWithPayload(L *lua.LState) int {
183177
body := L.CheckString(1)
184178
payload := L.CheckString(2)
185-
p := &luaPlugin{body: &body, jobPayload: &payload}
179+
p := NewLuaPlugin(L, 2)
180+
p.body = &body
181+
p.jobPayload = &payload
186182
ud := L.NewUserData()
187183
ud.Value = p
188184
L.SetMetatable(ud, L.GetTypeMetatable(`plugin_ud`))
@@ -191,10 +187,18 @@ func DoStringWithPayload(L *lua.LState) int {
191187
}
192188

193189
// Run lua plugin_ud:run()
194-
func Run(L *lua.LState) int {
190+
func Run(L *lua.LState) (nRet int) {
195191
p := checkPlugin(L, 1)
196192
go p.start()
197-
return 0
193+
194+
// ensure it's started
195+
p.Lock()
196+
defer p.Unlock()
197+
for !p.started {
198+
p.Cond.Wait()
199+
}
200+
201+
return
198202
}
199203

200204
// IsRunning lua plugin_ud:is_running()
@@ -204,6 +208,16 @@ func IsRunning(L *lua.LState) int {
204208
return 1
205209
}
206210

211+
// DoneChannel lua plugin_ud:done_chan()
212+
func DoneChannel(L *lua.LState) int {
213+
p := checkPlugin(L, 1)
214+
if !p.started {
215+
L.ArgError(1, "Cannot obtain done channel on unstarted plugin")
216+
}
217+
L.Push(p.doneCh)
218+
return 1
219+
}
220+
207221
// Error lua plugin_ud:error() returns err
208222
func Error(L *lua.LState) int {
209223
p := checkPlugin(L, 1)
@@ -223,3 +237,36 @@ func Stop(L *lua.LState) int {
223237
p.cancelFunc()
224238
return 0
225239
}
240+
241+
// Wait lua plugin_ud:wait()
242+
func Wait(L *lua.LState) int {
243+
p := checkPlugin(L, 1)
244+
// Can't wait if never started
245+
if !p.started {
246+
L.RaiseError("cannot wait on unstarted plugin")
247+
}
248+
// Add timeout if requested
249+
ctx := context.Background()
250+
cancel := func() {}
251+
timeout := lua.LVAsNumber(L.Get(2))
252+
if timeout > 0 {
253+
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout*lua.LNumber(time.Second)))
254+
defer cancel()
255+
}
256+
257+
// Wait for done or timeout
258+
var err error
259+
select {
260+
case <-ctx.Done():
261+
err = ctx.Err()
262+
case <-p.doneCh:
263+
err = p.error
264+
}
265+
266+
// return error
267+
if err != nil {
268+
L.Push(lua.LString(err.Error()))
269+
return 1
270+
}
271+
return 0
272+
}

plugin/api_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package plugin
22

33
import (
44
"github.com/stretchr/testify/assert"
5+
"github.com/vadv/gopher-lua-libs/inspect"
56
"github.com/vadv/gopher-lua-libs/tests"
67
"testing"
78

@@ -11,6 +12,7 @@ import (
1112

1213
func TestApi(t *testing.T) {
1314
preload := tests.SeveralPreloadFuncs(
15+
inspect.Preload,
1416
time.Preload,
1517
ioutil.Preload,
1618
Preload,

plugin/example_test.go

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,54 @@
11
package plugin
22

33
import (
4-
"log"
5-
64
time "github.com/vadv/gopher-lua-libs/time"
5+
"log"
76

87
lua "github.com/yuin/gopher-lua"
98
)
109

1110
// plugin.do_string(), plugin_ud:run(), plugin_ud:stop()
1211
func Example_package() {
1312
state := lua.NewState()
13+
defer state.Close()
1414
Preload(state)
1515
time.Preload(state)
1616
source := `
1717
local plugin = require("plugin")
1818
local time = require("time")
1919
2020
local plugin_body = [[
21-
local time = require("time")
21+
local doCh, doneCh = unpack(arg)
2222
local i = 1
23-
while true do
23+
while doCh:receive() do
2424
print(i)
25+
doneCh:send(i)
2526
i = i + 1
26-
time.sleep(1.01)
2727
end
2828
]]
2929
30-
local print_plugin = plugin.do_string(plugin_body)
30+
-- Make synchronization channels and fire up the plugin
31+
local doCh = channel.make(100)
32+
local doneCh = channel.make(100)
33+
local print_plugin = plugin.do_string(plugin_body, doCh, doneCh)
3134
print_plugin:run()
32-
time.sleep(2)
35+
36+
-- Allow two iterations to proceed
37+
doCh:send(nil)
38+
local ok, got = doneCh:receive()
39+
assert(ok and got == 1, string.format("ok = %s; got = %s", ok, got))
40+
doCh:send(nil)
41+
ok, got = doneCh:receive()
42+
assert(ok and got == 2, string.format("ok = %s; got = %s", ok, got))
43+
44+
-- Close the doCh and wait to ensure it's closed gracefully but stop just to be sure
45+
doCh:close()
46+
time.sleep(1)
3347
print_plugin:stop()
3448
time.sleep(1)
3549
36-
local running = print_plugin:is_running()
37-
if running then error("already running") end
38-
50+
-- Ensure it's not still running
51+
assert(not print_plugin:is_running(), "still running")
3952
`
4053
if err := state.DoString(source); err != nil {
4154
log.Fatal(err.Error())
@@ -44,3 +57,31 @@ func Example_package() {
4457
// 1
4558
// 2
4659
}
60+
61+
func Example_using_like_goroutine() {
62+
state := lua.NewState()
63+
defer state.Close()
64+
65+
Preload(state)
66+
67+
source := `
68+
local plugin = require 'plugin'
69+
70+
function myfunc(...)
71+
print(table.concat({...}, ""))
72+
end
73+
74+
local background_body = [[
75+
return pcall(unpack(arg))
76+
]]
77+
local background_plugin = plugin.do_string(background_body, myfunc, "Hello", " ", "World")
78+
background_plugin:run()
79+
local err = background_plugin:wait()
80+
assert(not err, err)
81+
`
82+
if err := state.DoString(source); err != nil {
83+
log.Fatal(err)
84+
}
85+
// Output:
86+
// Hello World
87+
}

plugin/loader.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ func Loader(L *lua.LState) int {
1818
pluginUd := L.NewTypeMetatable(`plugin_ud`)
1919
L.SetGlobal(`plugin_ud`, pluginUd)
2020
L.SetField(pluginUd, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{
21-
"run": Run,
22-
"error": Error,
23-
"stop": Stop,
24-
"is_running": IsRunning,
21+
"run": Run,
22+
"error": Error,
23+
"stop": Stop,
24+
"wait": Wait,
25+
"is_running": IsRunning,
26+
"done_channel": DoneChannel,
2527
}))
2628

2729
t := L.NewTable()

0 commit comments

Comments
 (0)