Skip to content

Commit

Permalink
std: Serialize resolution of deferred functions
Browse files Browse the repository at this point in the history
We want the js author to be able to rely on deterministic order of promise
resolution to avoid surprises and generate non-determinitic configuration.

We do this by making the order of promise resolution be the same as promise
resolution, serializing the deferred goroutines before they re-enter v8 to
resolve the promises.

Fixes: #110
  • Loading branch information
dlespiau committed Mar 5, 2019
1 parent 3de4c6d commit 937534b
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 5 deletions.
49 changes: 45 additions & 4 deletions pkg/deferred/deferred.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ import (
)

var (
globalDeferreds = &deferreds{}
globalDeferreds = newDeferreds()
)

func newDeferreds() *deferreds {
d := &deferreds{
serial: 1,
}
d.serialCond = sync.NewCond(&d.serialMu)
return d
}

// Register schedules an action to be performed later, with the result
// sent to `resolver`, using the global deferred scheduler.
func Register(p performFunc, r resolver) Serial {
Expand All @@ -37,13 +45,38 @@ func Wait() {
// JavaScript.
type Serial uint64

// To enforce determinism, we resolve deferred in the same order they are
// created. This is done through resolvedSerial that stores what was the last
// deferred resolved and we use a sync.Cond to handle synchronization between
// goroutines servicing the deferred.
type deferreds struct {
serialMu sync.Mutex
serial Serial
serialMu sync.Mutex
serial Serial
serialCond *sync.Cond
resolvedSerial Serial

outstanding sync.WaitGroup
}

func (d *deferreds) waitForSerial(s Serial) {
d.serialMu.Lock()
defer d.serialMu.Unlock()

for {
if d.resolvedSerial == s {
return
}
d.serialCond.Wait()
}
}

func (d *deferreds) serialResolved(s Serial) {
d.serialMu.Lock()
d.resolvedSerial = s
d.serialMu.Unlock()
d.serialCond.Broadcast()
}

// responder is the interface for a deferred request to use to send
// its response.
type resolver interface {
Expand All @@ -63,8 +96,16 @@ func (d *deferreds) Register(perform performFunc, r resolver) Serial {
d.serialMu.Unlock()
d.outstanding.Add(1)
go func(s Serial) {
defer d.outstanding.Done()
defer func() {
d.serialResolved(s)
d.outstanding.Done()
}()

b, err := perform()

// Wait for the serial-1 goroutine to be resolved.
d.waitForSerial(s - 1)

if err != nil {
r.Error(s, err)
return
Expand Down
2 changes: 1 addition & 1 deletion tests/test-deterministic-promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const resolvedOrder = [];
const checkRead = i => std.read('success.json', { encoding: std.Encoding.String }).then(() => resolvedOrder.push(i));

const promises = [];
for (let i = 0; i < 1000; i+=1) {
for (let i = 0; i < 1000; i += 1) {
promises.push(checkRead(i));
}

Expand Down

0 comments on commit 937534b

Please sign in to comment.