-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgoroutinevm.go
337 lines (300 loc) · 7.83 KB
/
goroutinevm.go
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
package tender
import (
"fmt"
"runtime/debug"
"sync/atomic"
"time"
)
func init() {
addBuiltinFunction("go", builtinGovm, true)
addBuiltinFunction("abort", builtinAbort, true)
addBuiltinFunction("makechan", builtinMakechan, false)
}
type ret struct {
val Object
err error
}
type goroutineVM struct {
*VM // if not nil, run CompiledFunction in VM
ret // return value
waitChan chan ret
done int64
}
// Starts a independent concurrent goroutine which runs fn(arg1, arg2, ...)
//
// If fn is CompiledFunction, the current running VM will be cloned to create
// a new VM in which the CompiledFunction will be running.
//
// The fn can also be any object that has Call() method, such as BuiltinFunction,
// in which case no cloned VM will be created.
//
// Returns a goroutineVM object that has wait, result, abort methods.
//
// The goroutineVM will not exit unless:
// 1. All its descendant goroutineVMs exit
// 2. It calls abort()
// 3. Its goroutineVM object abort() is called on behalf of its parent VM
// The latter 2 cases will trigger aborting procedure of all the descendant goroutineVMs,
// which will further result in #1 above.
func builtinGovm(args ...Object) (Object, error) {
vm := args[0].(*VMObj).Value
args = args[1:] // the first arg is VMObj inserted by VM
if len(args) == 0 {
return nil, ErrWrongNumArguments
}
fn := args[0]
if !fn.CanCall() {
return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "callable function",
Found: fn.TypeName(),
}
}
gvm := &goroutineVM{
waitChan: make(chan ret, 1),
}
var callers []frame
cfn, compiled := fn.(*CompiledFunction)
if compiled {
gvm.VM = vm.ShallowClone()
} else {
callers = vm.callers()
}
if err := vm.addChild(gvm.VM); err != nil {
return nil, err
}
go func() {
var val Object
var err error
defer func() {
if perr := recover(); perr != nil {
if callers == nil {
panic("callers not saved")
}
err = fmt.Errorf("\nRuntime Panic: %v%s\n%s", perr, vm.callStack(callers), debug.Stack())
}
if err != nil {
vm.addError(err)
}
gvm.waitChan <- ret{val, err}
vm.delChild(gvm.VM)
gvm.VM = nil
}()
if cfn != nil {
val, err = gvm.RunCompiled(cfn, args[1:]...)
} else {
var nargs []Object
if bltnfn, ok := fn.(*BuiltinFunction); ok {
if bltnfn.NeedVMObj {
// pass VM as the first para to builtin functions
nargs = append(nargs, vm.selfObject())
}
}
nargs = append(nargs, args[1:]...)
val, err = fn.Call(nargs...)
}
}()
obj := map[string]Object{
"result": &BuiltinFunction{Value: gvm.getRet},
"wait": &BuiltinFunction{Value: gvm.waitTimeout},
"abort": &BuiltinFunction{Value: gvm.abort},
}
return &Map{Value: obj}, nil
}
// Triggers the termination process of the current VM and all its descendant VMs.
func builtinAbort(args ...Object) (Object, error) {
vm := args[0].(*VMObj).Value
args = args[1:] // the first arg is VMObj inserted by VM
if len(args) != 0 {
return nil, ErrWrongNumArguments
}
vm.Abort() // aborts self and all descendant VMs
return nil, nil
}
// Returns true if the goroutineVM is done
func (gvm *goroutineVM) wait(seconds int64) bool {
if atomic.LoadInt64(&gvm.done) == 1 {
return true
}
if seconds < 0 {
seconds = 3153600000 // 100 years
}
select {
case gvm.ret = <-gvm.waitChan:
atomic.StoreInt64(&gvm.done, 1)
case <-time.After(time.Duration(seconds) * time.Second):
return false
}
return true
}
// Waits for the goroutineVM to complete up to timeout seconds.
// Returns true if the goroutineVM exited(successfully or not) within the timeout.
// Waits forever if the optional timeout not specified, or timeout < 0.
func (gvm *goroutineVM) waitTimeout(args ...Object) (Object, error) {
if len(args) > 1 {
return nil, ErrWrongNumArguments
}
timeOut := -1
if len(args) == 1 {
t, ok := ToInt(args[0])
if !ok {
return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
}
timeOut = t
}
if gvm.wait(int64(timeOut)) {
return TrueValue, nil
}
return FalseValue, nil
}
// Triggers the termination process of the goroutineVM and all its descendant VMs.
func (gvm *goroutineVM) abort(args ...Object) (Object, error) {
if len(args) != 0 {
return nil, ErrWrongNumArguments
}
if gvm.VM != nil {
gvm.Abort()
}
return nil, nil
}
// Waits the goroutineVM to complete, return Error object if any runtime error occurred
// during the execution, otherwise return the result value of fn(arg1, arg2, ...)
func (gvm *goroutineVM) getRet(args ...Object) (Object, error) {
if len(args) != 0 {
return nil, ErrWrongNumArguments
}
gvm.wait(-1)
if gvm.ret.err != nil {
return &Error{Value: &String{Value: gvm.ret.err.Error()}}, nil
}
return gvm.ret.val, nil
}
type objchan chan Object
// Makes a channel to send/receive object
// Returns a chan object that has send, recv, close methods.
func builtinMakechan(args ...Object) (Object, error) {
var size int
switch len(args) {
case 0:
case 1:
n, ok := ToInt(args[0])
if !ok {
return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
}
size = n
default:
return nil, ErrWrongNumArguments
}
oc := make(objchan, size)
obj := map[string]Object{
"send": &BuiltinFunction{Value: oc.send, NeedVMObj: true},
"recv": &BuiltinFunction{Value: oc.recv, NeedVMObj: true},
"close": &BuiltinFunction{Value: oc.close},
}
return &Map{Value: obj}, nil
}
// Sends an obj to the channel, will block if channel is full and the VM has not been aborted.
// Sends to a closed channel causes panic.
func (oc objchan) send(args ...Object) (Object, error) {
vm := args[0].(*VMObj).Value
args = args[1:] // the first arg is VMObj inserted by VM
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
select {
case <-vm.AbortChan:
return nil, ErrVMAborted
case oc <- args[0]:
}
return nil, nil
}
// Receives an obj from the channel, will block if channel is empty and the VM has not been aborted.
// Receives from a closed channel returns null value.
func (oc objchan) recv(args ...Object) (Object, error) {
vm := args[0].(*VMObj).Value
args = args[1:] // the first arg is VMObj inserted by VM
if len(args) != 0 {
return nil, ErrWrongNumArguments
}
select {
case <-vm.AbortChan:
return nil, ErrVMAborted
case obj, ok := <-oc:
if ok {
return obj, nil
}
}
return nil, nil
}
// Closes the channel.
func (oc objchan) close(args ...Object) (Object, error) {
if len(args) != 0 {
return nil, ErrWrongNumArguments
}
close(oc)
return nil, nil
}
func WrapFuncCall(vm *VM, args ...Object) (Object, error) {
if len(args) == 0 {
return nil, ErrWrongNumArguments
}
fn := args[0]
if !fn.CanCall() {
return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "callable function",
Found: fn.TypeName(),
}
}
gvm := &goroutineVM{
waitChan: make(chan ret, 1),
}
var callers []frame
cfn, compiled := fn.(*CompiledFunction)
if compiled {
gvm.VM = vm.ShallowClone()
} else {
callers = vm.callers()
}
if err := vm.addChild(gvm.VM); err != nil {
return nil, err
}
var val Object
var err error
defer func() {
if perr := recover(); perr != nil {
if callers == nil {
panic("callers not saved")
}
err = fmt.Errorf("\nRuntime Panic: %v%s\n%s", perr, vm.callStack(callers), debug.Stack())
}
if err != nil {
vm.addError(err)
}
gvm.waitChan <- ret{val, err}
vm.delChild(gvm.VM)
gvm.VM = nil
}()
if cfn != nil {
val, err = gvm.RunCompiled(cfn, args[1:]...)
} else {
var nargs []Object
if bltnfn, ok := fn.(*BuiltinFunction); ok {
if bltnfn.NeedVMObj {
// pass VM as the first para to builtin functions
nargs = append(nargs, vm.selfObject())
}
}
nargs = append(nargs, args[1:]...)
val, err = fn.Call(nargs...)
}
return val, nil
}