Skip to content

Commit c54d123

Browse files
committed
setTimeout and sendAsync implemented
added and eval queue for serializing JSRE vm execution
1 parent 2e9ed6f commit c54d123

File tree

4 files changed

+299
-35
lines changed

4 files changed

+299
-35
lines changed

cmd/geth/js.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ func (js *jsre) apiBindings() {
103103
t, _ := js.re.Get("jeth")
104104
jethObj := t.Object()
105105
jethObj.Set("send", jeth.Send)
106+
jethObj.Set("sendAsync", jeth.Send)
106107

107108
err := js.re.Compile("bignumber.js", re.BigNumber_JS)
108109
if err != nil {
@@ -172,8 +173,10 @@ func (self *jsre) UnlockAccount(addr []byte) bool {
172173

173174
func (self *jsre) exec(filename string) error {
174175
if err := self.re.Exec(filename); err != nil {
176+
self.re.Stop(false)
175177
return fmt.Errorf("Javascript Error: %v", err)
176178
}
179+
self.re.Stop(true)
177180
return nil
178181
}
179182

@@ -201,6 +204,7 @@ func (self *jsre) interactive() {
201204
if self.atexit != nil {
202205
self.atexit()
203206
}
207+
self.re.Stop(false)
204208
}
205209

206210
func (self *jsre) withHistory(op func(*os.File)) {

jsre/jsre.go

Lines changed: 220 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package jsre
33
import (
44
"fmt"
55
"io/ioutil"
6-
7-
"github.com/robertkrimen/otto"
6+
"sync"
7+
"time"
88

99
"github.com/ethereum/go-ethereum/common"
10+
"github.com/robertkrimen/otto"
1011
)
1112

1213
/*
@@ -20,66 +21,269 @@ It provides some helper functions to
2021
type JSRE struct {
2122
assetPath string
2223
vm *otto.Otto
24+
25+
evalQueue chan *evalReq
26+
stopEventLoop chan bool
27+
loopWg sync.WaitGroup
28+
}
29+
30+
// jsTimer is a single timer instance with a callback function
31+
type jsTimer struct {
32+
timer *time.Timer
33+
duration time.Duration
34+
interval bool
35+
call otto.FunctionCall
2336
}
2437

38+
// evalResult is a structure to store the result of any serialized vm execution
39+
type evalResult struct {
40+
result otto.Value
41+
err error
42+
}
43+
44+
// evalReq is a serialized vm execution request put in evalQueue and processed by runEventLoop
45+
type evalReq struct {
46+
fn func(res *evalResult)
47+
done chan bool
48+
res evalResult
49+
}
50+
51+
// runtime must be stopped with Stop() after use and cannot be used after stopping
2552
func New(assetPath string) *JSRE {
2653
re := &JSRE{
27-
assetPath,
28-
otto.New(),
54+
assetPath: assetPath,
55+
vm: otto.New(),
2956
}
3057

3158
// load prettyprint func definition
3259
re.vm.Run(pp_js)
3360
re.vm.Set("loadScript", re.loadScript)
3461

62+
re.evalQueue = make(chan *evalReq)
63+
re.stopEventLoop = make(chan bool)
64+
re.loopWg.Add(1)
65+
go re.runEventLoop()
66+
3567
return re
3668
}
3769

70+
// this function runs a piece of JS code either in a serialized way (when useEQ is true) or instantly, circumventing the evalQueue
71+
func (self *JSRE) run(src interface{}, useEQ bool) (value otto.Value, err error) {
72+
if useEQ {
73+
done := make(chan bool)
74+
req := &evalReq{
75+
fn: func(res *evalResult) {
76+
res.result, res.err = self.vm.Run(src)
77+
},
78+
done: done,
79+
}
80+
self.evalQueue <- req
81+
<-done
82+
return req.res.result, req.res.err
83+
} else {
84+
return self.vm.Run(src)
85+
}
86+
}
87+
88+
/*
89+
This function runs the main event loop from a goroutine that is started
90+
when JSRE is created. Use Stop() before exiting to properly stop it.
91+
The event loop processes vm access requests from the evalQueue in a
92+
serialized way and calls timer callback functions at the appropriate time.
93+
94+
Exported functions always access the vm through the event queue. You can
95+
call the functions of the otto vm directly to circumvent the queue. These
96+
functions should be used if and only if running a routine that was already
97+
called from JS through an RPC call.
98+
*/
99+
func (self *JSRE) runEventLoop() {
100+
registry := map[*jsTimer]*jsTimer{}
101+
ready := make(chan *jsTimer)
102+
103+
newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) {
104+
105+
delay, _ := call.Argument(1).ToInteger()
106+
if 0 >= delay {
107+
delay = 1
108+
}
109+
timer := &jsTimer{
110+
duration: time.Duration(delay) * time.Millisecond,
111+
call: call,
112+
interval: interval,
113+
}
114+
registry[timer] = timer
115+
116+
timer.timer = time.AfterFunc(timer.duration, func() {
117+
ready <- timer
118+
})
119+
120+
value, err := call.Otto.ToValue(timer)
121+
if err != nil {
122+
panic(err)
123+
}
124+
125+
return timer, value
126+
}
127+
128+
setTimeout := func(call otto.FunctionCall) otto.Value {
129+
_, value := newTimer(call, false)
130+
return value
131+
}
132+
133+
setInterval := func(call otto.FunctionCall) otto.Value {
134+
_, value := newTimer(call, true)
135+
return value
136+
}
137+
138+
clearTimeout := func(call otto.FunctionCall) otto.Value {
139+
timer, _ := call.Argument(0).Export()
140+
if timer, ok := timer.(*jsTimer); ok {
141+
timer.timer.Stop()
142+
delete(registry, timer)
143+
}
144+
return otto.UndefinedValue()
145+
}
146+
147+
var waitForCallbacks bool
148+
149+
loop:
150+
for {
151+
select {
152+
case timer := <-ready:
153+
// execute callback, remove/reschedule the timer
154+
var arguments []interface{}
155+
if len(timer.call.ArgumentList) > 2 {
156+
tmp := timer.call.ArgumentList[2:]
157+
arguments = make([]interface{}, 2+len(tmp))
158+
for i, value := range tmp {
159+
arguments[i+2] = value
160+
}
161+
} else {
162+
arguments = make([]interface{}, 1)
163+
}
164+
arguments[0] = timer.call.ArgumentList[0]
165+
_, err := self.vm.Call(`Function.call.call`, nil, arguments...)
166+
167+
if err != nil {
168+
break loop
169+
}
170+
if timer.interval {
171+
timer.timer.Reset(timer.duration)
172+
} else {
173+
delete(registry, timer)
174+
if waitForCallbacks && (len(registry) == 0) {
175+
break loop
176+
}
177+
}
178+
case evalReq := <-self.evalQueue:
179+
// run the code, send the result back
180+
self.vm.Set("setTimeout", setTimeout)
181+
self.vm.Set("setInterval", setInterval)
182+
self.vm.Set("clearTimeout", clearTimeout)
183+
self.vm.Set("clearInterval", clearTimeout)
184+
evalReq.fn(&evalReq.res)
185+
close(evalReq.done)
186+
if waitForCallbacks && (len(registry) == 0) {
187+
break loop
188+
}
189+
case waitForCallbacks = <-self.stopEventLoop:
190+
if !waitForCallbacks || (len(registry) == 0) {
191+
break loop
192+
}
193+
}
194+
}
195+
196+
for _, timer := range registry {
197+
timer.timer.Stop()
198+
delete(registry, timer)
199+
}
200+
201+
self.loopWg.Done()
202+
}
203+
204+
// stops the event loop before exit, optionally waits for all timers to expire
205+
func (self *JSRE) Stop(waitForCallbacks bool) {
206+
self.stopEventLoop <- waitForCallbacks
207+
self.loopWg.Wait()
208+
}
209+
38210
// Exec(file) loads and runs the contents of a file
39211
// if a relative path is given, the jsre's assetPath is used
40212
func (self *JSRE) Exec(file string) error {
41-
return self.exec(common.AbsolutePath(self.assetPath, file))
213+
return self.exec(common.AbsolutePath(self.assetPath, file), true)
42214
}
43215

44-
func (self *JSRE) exec(path string) error {
216+
// circumvents the eval queue, see runEventLoop
217+
func (self *JSRE) execWithoutEQ(file string) error {
218+
return self.exec(common.AbsolutePath(self.assetPath, file), false)
219+
}
220+
221+
func (self *JSRE) exec(path string, useEQ bool) error {
45222
code, err := ioutil.ReadFile(path)
46223
if err != nil {
47224
return err
48225
}
49-
_, err = self.vm.Run(code)
226+
_, err = self.run(code, useEQ)
50227
return err
51228
}
52229

230+
// assigns value v to a variable in the JS environment
53231
func (self *JSRE) Bind(name string, v interface{}) (err error) {
54-
self.vm.Set(name, v)
232+
self.Set(name, v)
55233
return
56234
}
57235

236+
// runs a piece of JS code
58237
func (self *JSRE) Run(code string) (otto.Value, error) {
59-
return self.vm.Run(code)
238+
return self.run(code, true)
60239
}
61240

241+
// returns the value of a variable in the JS environment
62242
func (self *JSRE) Get(ns string) (otto.Value, error) {
63-
return self.vm.Get(ns)
243+
done := make(chan bool)
244+
req := &evalReq{
245+
fn: func(res *evalResult) {
246+
res.result, res.err = self.vm.Get(ns)
247+
},
248+
done: done,
249+
}
250+
self.evalQueue <- req
251+
<-done
252+
return req.res.result, req.res.err
64253
}
65254

255+
// assigns value v to a variable in the JS environment
66256
func (self *JSRE) Set(ns string, v interface{}) error {
67-
return self.vm.Set(ns, v)
257+
done := make(chan bool)
258+
req := &evalReq{
259+
fn: func(res *evalResult) {
260+
res.err = self.vm.Set(ns, v)
261+
},
262+
done: done,
263+
}
264+
self.evalQueue <- req
265+
<-done
266+
return req.res.err
68267
}
69268

269+
/*
270+
Executes a JS script from inside the currently executing JS code.
271+
Should only be called from inside an RPC routine.
272+
*/
70273
func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
71274
file, err := call.Argument(0).ToString()
72275
if err != nil {
73276
return otto.FalseValue()
74277
}
75-
if err := self.Exec(file); err != nil {
278+
if err := self.execWithoutEQ(file); err != nil { // loadScript is only called from inside js
76279
fmt.Println("err:", err)
77280
return otto.FalseValue()
78281
}
79282

80283
return otto.TrueValue()
81284
}
82285

286+
// uses the "prettyPrint" JS function to format a value
83287
func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
84288
var method otto.Value
85289
v, err = self.vm.ToValue(v)
@@ -93,6 +297,7 @@ func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
93297
return method.Call(method, v)
94298
}
95299

300+
// creates an otto value from a go type
96301
func (self *JSRE) ToVal(v interface{}) otto.Value {
97302
result, err := self.vm.ToValue(v)
98303
if err != nil {
@@ -102,6 +307,7 @@ func (self *JSRE) ToVal(v interface{}) otto.Value {
102307
return result
103308
}
104309

310+
// evaluates JS function and returns result in a pretty printed string format
105311
func (self *JSRE) Eval(code string) (s string, err error) {
106312
var val otto.Value
107313
val, err = self.Run(code)
@@ -115,11 +321,12 @@ func (self *JSRE) Eval(code string) (s string, err error) {
115321
return fmt.Sprintf("%v", val), nil
116322
}
117323

324+
// compiles and then runs a piece of JS code
118325
func (self *JSRE) Compile(fn string, src interface{}) error {
119326
script, err := self.vm.Compile(fn, src)
120327
if err != nil {
121328
return err
122329
}
123-
self.vm.Run(script)
330+
self.run(script, true)
124331
return nil
125332
}

0 commit comments

Comments
 (0)