@@ -3,10 +3,11 @@ package jsre
3
3
import (
4
4
"fmt"
5
5
"io/ioutil"
6
-
7
- "github.com/robertkrimen/otto "
6
+ "sync"
7
+ "time "
8
8
9
9
"github.com/ethereum/go-ethereum/common"
10
+ "github.com/robertkrimen/otto"
10
11
)
11
12
12
13
/*
@@ -20,66 +21,269 @@ It provides some helper functions to
20
21
type JSRE struct {
21
22
assetPath string
22
23
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
23
36
}
24
37
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
25
52
func New (assetPath string ) * JSRE {
26
53
re := & JSRE {
27
- assetPath ,
28
- otto .New (),
54
+ assetPath : assetPath ,
55
+ vm : otto .New (),
29
56
}
30
57
31
58
// load prettyprint func definition
32
59
re .vm .Run (pp_js )
33
60
re .vm .Set ("loadScript" , re .loadScript )
34
61
62
+ re .evalQueue = make (chan * evalReq )
63
+ re .stopEventLoop = make (chan bool )
64
+ re .loopWg .Add (1 )
65
+ go re .runEventLoop ()
66
+
35
67
return re
36
68
}
37
69
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
+
38
210
// Exec(file) loads and runs the contents of a file
39
211
// if a relative path is given, the jsre's assetPath is used
40
212
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 )
42
214
}
43
215
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 {
45
222
code , err := ioutil .ReadFile (path )
46
223
if err != nil {
47
224
return err
48
225
}
49
- _ , err = self .vm . Run (code )
226
+ _ , err = self .run (code , useEQ )
50
227
return err
51
228
}
52
229
230
+ // assigns value v to a variable in the JS environment
53
231
func (self * JSRE ) Bind (name string , v interface {}) (err error ) {
54
- self .vm . Set (name , v )
232
+ self .Set (name , v )
55
233
return
56
234
}
57
235
236
+ // runs a piece of JS code
58
237
func (self * JSRE ) Run (code string ) (otto.Value , error ) {
59
- return self .vm . Run (code )
238
+ return self .run (code , true )
60
239
}
61
240
241
+ // returns the value of a variable in the JS environment
62
242
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
64
253
}
65
254
255
+ // assigns value v to a variable in the JS environment
66
256
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
68
267
}
69
268
269
+ /*
270
+ Executes a JS script from inside the currently executing JS code.
271
+ Should only be called from inside an RPC routine.
272
+ */
70
273
func (self * JSRE ) loadScript (call otto.FunctionCall ) otto.Value {
71
274
file , err := call .Argument (0 ).ToString ()
72
275
if err != nil {
73
276
return otto .FalseValue ()
74
277
}
75
- if err := self .Exec (file ); err != nil {
278
+ if err := self .execWithoutEQ (file ); err != nil { // loadScript is only called from inside js
76
279
fmt .Println ("err:" , err )
77
280
return otto .FalseValue ()
78
281
}
79
282
80
283
return otto .TrueValue ()
81
284
}
82
285
286
+ // uses the "prettyPrint" JS function to format a value
83
287
func (self * JSRE ) PrettyPrint (v interface {}) (val otto.Value , err error ) {
84
288
var method otto.Value
85
289
v , err = self .vm .ToValue (v )
@@ -93,6 +297,7 @@ func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
93
297
return method .Call (method , v )
94
298
}
95
299
300
+ // creates an otto value from a go type
96
301
func (self * JSRE ) ToVal (v interface {}) otto.Value {
97
302
result , err := self .vm .ToValue (v )
98
303
if err != nil {
@@ -102,6 +307,7 @@ func (self *JSRE) ToVal(v interface{}) otto.Value {
102
307
return result
103
308
}
104
309
310
+ // evaluates JS function and returns result in a pretty printed string format
105
311
func (self * JSRE ) Eval (code string ) (s string , err error ) {
106
312
var val otto.Value
107
313
val , err = self .Run (code )
@@ -115,11 +321,12 @@ func (self *JSRE) Eval(code string) (s string, err error) {
115
321
return fmt .Sprintf ("%v" , val ), nil
116
322
}
117
323
324
+ // compiles and then runs a piece of JS code
118
325
func (self * JSRE ) Compile (fn string , src interface {}) error {
119
326
script , err := self .vm .Compile (fn , src )
120
327
if err != nil {
121
328
return err
122
329
}
123
- self .vm . Run (script )
330
+ self .run (script , true )
124
331
return nil
125
332
}
0 commit comments