Skip to content

Commit 192a49e

Browse files
committed
Update sobek to fix interrupt with async code
Also fix it through the codebase, although experimental/streams and fs likely will need more work.
1 parent 461c779 commit 192a49e

17 files changed

+140
-90
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ require (
7676
github.com/go-logr/stdr v1.2.2 // indirect
7777
github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 // indirect
7878
github.com/google/uuid v1.6.0 // indirect
79-
github.com/grafana/sobek v0.0.0-20241023145759-2dc9daf5bfa2
79+
github.com/grafana/sobek v0.0.0-20241024150027-d91f02b05e9b
8080
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
8181
github.com/inconshreveable/mousetrap v1.0.0 // indirect
8282
github.com/josharian/intern v1.0.0 // indirect

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
8787
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
8888
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
8989
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
90-
github.com/grafana/sobek v0.0.0-20241023145759-2dc9daf5bfa2 h1:qgthy9RbAIxinOmXaB9nSaT/w00VTqeEQ7JI0a+ScUU=
91-
github.com/grafana/sobek v0.0.0-20241023145759-2dc9daf5bfa2/go.mod h1:FmcutBFPLiGgroH42I4/HBahv7GxVjODcVWFTw1ISes=
90+
github.com/grafana/sobek v0.0.0-20241024150027-d91f02b05e9b h1:hzfIt1lf19Zx1jIYdeHvuWS266W+jL+7dxbpvH2PZMQ=
91+
github.com/grafana/sobek v0.0.0-20241024150027-d91f02b05e9b/go.mod h1:FmcutBFPLiGgroH42I4/HBahv7GxVjODcVWFTw1ISes=
9292
github.com/grafana/xk6-browser v1.8.5 h1:dNAG8dhcaEx/HOELEnGzAw8ShCvkpukfyTGUhebZsj0=
9393
github.com/grafana/xk6-browser v1.8.5/go.mod h1:yCtZ4G8U/imVBikBO4HJoMyNoejmECcJk4CK5XGSxis=
9494
github.com/grafana/xk6-dashboard v0.7.5 h1:TcILyffT/Ea/XD7xG1jMA5lwtusOPRbEQsQDHmO30Mk=

js/modules/cjsmodule.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ func (cmi *cjsModuleInstance) HasTLA() bool { return false }
5858

5959
func (cmi *cjsModuleInstance) RequestedModules() []string { return cmi.w.RequestedModules() }
6060

61-
func (cmi *cjsModuleInstance) ExecuteModule(rt *sobek.Runtime, _, _ func(any)) (sobek.CyclicModuleInstance, error) {
61+
func (cmi *cjsModuleInstance) ExecuteModule(
62+
rt *sobek.Runtime, _, _ func(any) error,
63+
) (sobek.CyclicModuleInstance, error) {
6264
v, err := rt.RunProgram(cmi.w.prg)
6365
if err != nil {
6466
return nil, err

js/modules/gomodule.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type goModuleInstance struct {
6060
defaultExport sobek.Value
6161
}
6262

63-
func (gmi *goModuleInstance) ExecuteModule(_ *sobek.Runtime, _, _ func(any)) (sobek.CyclicModuleInstance, error) {
63+
func (gmi *goModuleInstance) ExecuteModule(_ *sobek.Runtime, _, _ func(any) error) (sobek.CyclicModuleInstance, error) {
6464
return gmi, nil
6565
}
6666
func (gmi *goModuleInstance) HasTLA() bool { return false }

js/modules/gomodule_basic.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ func (bgmi *basicGoModuleInstance) GetBindingValue(n string) sobek.Value {
6767

6868
func (bgmi *basicGoModuleInstance) HasTLA() bool { return false }
6969

70-
func (bgmi *basicGoModuleInstance) ExecuteModule(_ *sobek.Runtime, _, _ func(any)) (sobek.CyclicModuleInstance, error) {
70+
func (bgmi *basicGoModuleInstance) ExecuteModule(
71+
_ *sobek.Runtime, _, _ func(any) error,
72+
) (sobek.CyclicModuleInstance, error) {
7173
return bgmi, nil
7274
}

js/modules/k6/experimental/fs/module.go

+23-29
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"go.k6.io/k6/lib/fsext"
1414

1515
"github.com/grafana/sobek"
16+
1617
"go.k6.io/k6/js/common"
1718
"go.k6.io/k6/js/modules"
1819
"go.k6.io/k6/js/promises"
@@ -218,25 +219,25 @@ func (f *File) Stat() *sobek.Promise {
218219
//
219220
// It is possible for a read to successfully return with 0 bytes.
220221
// This does not indicate EOF.
221-
func (f *File) Read(into sobek.Value) *sobek.Promise {
222+
func (f *File) Read(into sobek.Value) (*sobek.Promise, error) {
222223
promise, resolve, reject := f.vu.Runtime().NewPromise()
223224

224225
if common.IsNullish(into) {
225-
reject(newFsError(TypeError, "read() failed; reason: into argument cannot be null or undefined"))
226-
return promise
226+
err := reject(newFsError(TypeError, "read() failed; reason: into argument cannot be null or undefined"))
227+
return promise, err
227228
}
228229

229230
intoObj := into.ToObject(f.vu.Runtime())
230231
if !isUint8Array(f.vu.Runtime(), intoObj) {
231-
reject(newFsError(TypeError, "read() failed; reason: into argument must be a Uint8Array"))
232-
return promise
232+
err := reject(newFsError(TypeError, "read() failed; reason: into argument must be a Uint8Array"))
233+
return promise, err
233234
}
234235

235236
// Obtain the underlying ArrayBuffer from the Uint8Array
236237
ab, ok := intoObj.Get("buffer").Export().(sobek.ArrayBuffer)
237238
if !ok {
238-
reject(newFsError(TypeError, "read() failed; reason: into argument must be a Uint8Array"))
239-
return promise
239+
err := reject(newFsError(TypeError, "read() failed; reason: into argument must be a Uint8Array"))
240+
return promise, err
240241
}
241242

242243
// To avoid concurrency linked to modifying the runtime's `into` buffer from multiple
@@ -257,8 +258,7 @@ func (f *File) Read(into sobek.Value) *sobek.Promise {
257258
// Read was successful, resolve early with the number of
258259
// bytes read.
259260
if readErr == nil {
260-
resolve(n)
261-
return nil
261+
return resolve(n)
262262
}
263263

264264
// If the read operation failed, we need to check if it was an io.EOF error
@@ -270,67 +270,61 @@ func (f *File) Read(into sobek.Value) *sobek.Promise {
270270
var fsErr *fsError
271271
isFSErr := errors.As(readErr, &fsErr)
272272
if !isFSErr {
273-
reject(readErr)
274-
return nil
273+
return reject(readErr)
275274
}
276275

277276
if fsErr.kind == EOFError && n == 0 {
278-
resolve(sobek.Null())
279-
} else {
280-
resolve(n)
277+
return resolve(sobek.Null())
281278
}
282-
283-
return nil
279+
return resolve(n)
284280
})
285281
}()
286282

287-
return promise
283+
return promise, nil
288284
}
289285

290286
// Seek seeks to the given `offset` in the file, under the given `whence` mode.
291287
//
292288
// The returned promise resolves to the new `offset` (position) within the file, which
293289
// is expressed in bytes from the selected start, current, or end position depending
294290
// the provided `whence`.
295-
func (f *File) Seek(offset sobek.Value, whence sobek.Value) *sobek.Promise {
291+
func (f *File) Seek(offset sobek.Value, whence sobek.Value) (*sobek.Promise, error) {
296292
promise, resolve, reject := f.vu.Runtime().NewPromise()
297293

298294
intOffset, err := exportInt(offset)
299295
if err != nil {
300-
reject(newFsError(TypeError, "seek() failed; reason: the offset argument "+err.Error()))
301-
return promise
296+
err := reject(newFsError(TypeError, "seek() failed; reason: the offset argument "+err.Error()))
297+
return promise, err
302298
}
303299

304300
intWhence, err := exportInt(whence)
305301
if err != nil {
306-
reject(newFsError(TypeError, "seek() failed; reason: the whence argument "+err.Error()))
307-
return promise
302+
err := reject(newFsError(TypeError, "seek() failed; reason: the whence argument "+err.Error()))
303+
return promise, err
308304
}
309305

310306
seekMode := SeekMode(intWhence)
311307
switch seekMode {
312308
case SeekModeStart, SeekModeCurrent, SeekModeEnd:
313309
// Valid modes, do nothing.
314310
default:
315-
reject(newFsError(TypeError, "seek() failed; reason: the whence argument must be a SeekMode"))
316-
return promise
311+
err := reject(newFsError(TypeError, "seek() failed; reason: the whence argument must be a SeekMode"))
312+
return promise, err
317313
}
318314

319315
callback := f.vu.RegisterCallback()
320316
go func() {
321317
newOffset, err := f.ReadSeekStater.Seek(intOffset, seekMode)
322318
callback(func() error {
323319
if err != nil {
324-
reject(err)
325-
return err
320+
return reject(err)
326321
}
327322

328-
resolve(newOffset)
329-
return nil
323+
return resolve(newOffset)
330324
})
331325
}()
332326

333-
return promise
327+
return promise, nil
334328
}
335329

336330
func isUint8Array(rt *sobek.Runtime, o *sobek.Object) bool {

js/modules/k6/experimental/streams/goja.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,29 @@ import (
55
"reflect"
66

77
"github.com/grafana/sobek"
8+
89
"go.k6.io/k6/js/common"
910
"go.k6.io/k6/js/modules"
1011
)
1112

1213
// newResolvedPromise instantiates a new resolved promise.
1314
func newResolvedPromise(vu modules.VU, with sobek.Value) *sobek.Promise {
1415
promise, resolve, _ := vu.Runtime().NewPromise()
15-
resolve(with)
16+
err := resolve(with)
17+
if err != nil { // TODO(@mstoykov): likely better to actually call Promise.resolve directly
18+
panic(err)
19+
}
20+
1621
return promise
1722
}
1823

1924
// newRejectedPromise instantiates a new rejected promise.
2025
func newRejectedPromise(vu modules.VU, with any) *sobek.Promise {
2126
promise, _, reject := vu.Runtime().NewPromise()
22-
reject(with)
27+
err := reject(with)
28+
if err != nil {
29+
panic(err)
30+
}
2331
return promise
2432
}
2533

js/modules/k6/experimental/streams/readable_stream_default_reader.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package streams
22

33
import (
44
"github.com/grafana/sobek"
5+
56
"go.k6.io/k6/js/common"
67
)
78

@@ -63,15 +64,25 @@ func (reader *ReadableStreamDefaultReader) Read() *sobek.Promise {
6364
readRequest := ReadRequest{
6465
chunkSteps: func(chunk any) {
6566
// Resolve promise with «[ "value" → chunk, "done" → false ]».
66-
resolve(map[string]any{"value": chunk, "done": false})
67+
// TODO(@mstoykov): propagate as error?
68+
err := resolve(map[string]any{"value": chunk, "done": false})
69+
if err != nil {
70+
panic(err)
71+
}
6772
},
6873
closeSteps: func() {
6974
// Resolve promise with «[ "value" → undefined, "done" → true ]».
70-
resolve(map[string]any{"value": sobek.Undefined(), "done": true})
75+
err := resolve(map[string]any{"value": sobek.Undefined(), "done": true})
76+
if err != nil {
77+
panic(err)
78+
}
7179
},
7280
errorSteps: func(e any) {
7381
// Reject promise with e.
74-
reject(e)
82+
err := reject(e)
83+
if err != nil {
84+
panic(err)
85+
}
7586
},
7687
}
7788

js/modules/k6/experimental/streams/readable_stream_reader.go

+23-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package streams
22

33
import (
44
"github.com/grafana/sobek"
5+
56
"go.k6.io/k6/js/common"
67
"go.k6.io/k6/js/modules"
78
)
@@ -35,10 +36,10 @@ type ReadableStreamGenericReader interface {
3536
SetStream(stream *ReadableStream)
3637

3738
// GetClosed returns a [sobek.Promise] that resolves when the stream is closed.
38-
GetClosed() (p *sobek.Promise, resolve func(any), reject func(any))
39+
GetClosed() (p *sobek.Promise, resolve, reject func(any) error)
3940

4041
// SetClosed sets the [sobek.Promise] that resolves when the stream is closed.
41-
SetClosed(p *sobek.Promise, resolve func(any), reject func(any))
42+
SetClosed(p *sobek.Promise, resolve, reject func(any) error)
4243

4344
// Cancel returns a [sobek.Promise] that resolves when the stream is canceled.
4445
Cancel(reason sobek.Value) *sobek.Promise
@@ -47,8 +48,8 @@ type ReadableStreamGenericReader interface {
4748
// BaseReadableStreamReader is a base implement
4849
type BaseReadableStreamReader struct {
4950
closedPromise *sobek.Promise
50-
closedPromiseResolveFunc func(resolve any)
51-
closedPromiseRejectFunc func(reason any)
51+
closedPromiseResolveFunc func(resolve any) error
52+
closedPromiseRejectFunc func(reason any) error
5253

5354
// stream is a [ReadableStream] instance that owns this reader
5455
stream *ReadableStream
@@ -73,12 +74,12 @@ func (reader *BaseReadableStreamReader) SetStream(stream *ReadableStream) {
7374
}
7475

7576
// GetClosed returns the reader's closed promise as well as its resolve and reject functions.
76-
func (reader *BaseReadableStreamReader) GetClosed() (p *sobek.Promise, resolve func(any), reject func(any)) {
77+
func (reader *BaseReadableStreamReader) GetClosed() (p *sobek.Promise, resolve, reject func(any) error) {
7778
return reader.closedPromise, reader.closedPromiseResolveFunc, reader.closedPromiseRejectFunc
7879
}
7980

8081
// SetClosed sets the reader's closed promise as well as its resolve and reject functions.
81-
func (reader *BaseReadableStreamReader) SetClosed(p *sobek.Promise, resolve func(any), reject func(any)) {
82+
func (reader *BaseReadableStreamReader) SetClosed(p *sobek.Promise, resolve, reject func(any) error) {
8283
reader.closedPromise = p
8384
reader.closedPromiseResolveFunc = resolve
8485
reader.closedPromiseRejectFunc = reject
@@ -133,7 +134,10 @@ func (reader *BaseReadableStreamReader) release() {
133134

134135
// 4. If stream.[[state]] is "readable", reject reader.[[closedPromise]] with a TypeError exception.
135136
if stream.state == ReadableStreamStateReadable {
136-
reader.closedPromiseRejectFunc(newTypeError(reader.runtime, "stream is readable").Err())
137+
err := reader.closedPromiseRejectFunc(newTypeError(reader.runtime, "stream is readable").Err())
138+
if err != nil {
139+
panic(err)
140+
}
137141
} else { // 5. Otherwise, set reader.[[closedPromise]] to a promise rejected with a TypeError exception.
138142
reader.closedPromise = newRejectedPromise(stream.vu, newTypeError(reader.runtime, "stream is not readable").Err())
139143
}
@@ -196,7 +200,10 @@ func ReadableStreamReaderGenericInitialize(reader ReadableStreamGenericReader, s
196200
// 4. Otherwise, if stream.[[state]] is "closed",
197201
case ReadableStreamStateClosed:
198202
// 4.1 Set reader.[[closedPromise]] to a promise resolved with undefined.
199-
resolve(sobek.Undefined())
203+
err := resolve(sobek.Undefined())
204+
if err != nil {
205+
panic(err) // TODO(@mstoykov): probably better to move them out as errors
206+
}
200207
// 5. Otherwise,
201208
default:
202209
// 5.1 Assert: stream.[[state]] is "errored".
@@ -206,9 +213,15 @@ func ReadableStreamReaderGenericInitialize(reader ReadableStreamGenericReader, s
206213

207214
// 5.2 Set reader.[[closedPromise]] to a promise rejected with stream.[[storedError]].
208215
if jsErr, ok := stream.storedError.(*jsError); ok {
209-
reject(jsErr.Err())
216+
err := reject(jsErr.Err())
217+
if err != nil {
218+
panic(err)
219+
}
210220
} else {
211-
reject(errToObj(stream.runtime, stream.storedError))
221+
err := reject(errToObj(stream.runtime, stream.storedError))
222+
if err != nil {
223+
panic(err)
224+
}
212225
}
213226

214227
// 5.3 Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.

js/modules/k6/experimental/streams/readable_streams.go

+12-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55

66
"github.com/grafana/sobek"
7+
78
"go.k6.io/k6/js/common"
89
"go.k6.io/k6/js/modules"
910
"go.k6.io/k6/js/promises"
@@ -456,7 +457,10 @@ func (stream *ReadableStream) close() {
456457
}
457458

458459
_, resolveFunc, _ := genericReader.GetClosed()
459-
resolveFunc(sobek.Undefined())
460+
err := resolveFunc(sobek.Undefined())
461+
if err != nil {
462+
panic(err) // TODO(@mstoykov): propagate as error instead
463+
}
460464

461465
// 6. If reader implements ReadableStreamDefaultReader,
462466
defaultReader, ok := reader.(*ReadableStreamDefaultReader)
@@ -503,19 +507,20 @@ func (stream *ReadableStream) error(e any) {
503507
}
504508

505509
// 6. Reject reader.[[closedPromise]] with e.
510+
var err error
506511
promise, _, rejectFunc := genericReader.GetClosed()
507512
if jsErr, ok := e.(*jsError); ok {
508-
rejectFunc(jsErr.Err())
513+
err = rejectFunc(jsErr.Err())
509514
} else {
510-
rejectFunc(e)
515+
err = rejectFunc(e)
516+
}
517+
if err != nil {
518+
panic(err) // TODO(@mstoykov): propagate as error instead
511519
}
512520

513521
// 7. Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
514522
// See https://github.com/dop251/goja/issues/565
515-
var (
516-
err error
517-
doNothing = func(sobek.Value) {}
518-
)
523+
doNothing := func(sobek.Value) {}
519524
_, err = promiseThen(stream.vu.Runtime(), promise, doNothing, doNothing)
520525
if err != nil {
521526
common.Throw(stream.vu.Runtime(), newError(RuntimeError, err.Error()))

0 commit comments

Comments
 (0)