Skip to content

Commit

Permalink
Merge pull request #143 from jkcfg/2019-03-05-deterministic-promise-r…
Browse files Browse the repository at this point in the history
…esolution

Deterministic promise resolution
  • Loading branch information
dlespiau authored Mar 6, 2019
2 parents 217500a + a333749 commit c674536
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 4 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
9 changes: 9 additions & 0 deletions tests/test-deterministic-promises.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import std from '@jkcfg/std';

const resolvedOrder = [];
const promises = [];
for (let i = 0; i < 100; i += 1) {
promises.push(std.read('success.json').then(() => resolvedOrder.push(i)));
}

Promise.all(promises).then(() => std.log(resolvedOrder.join(' ')));
1 change: 1 addition & 0 deletions tests/test-deterministic-promises.js.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0 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

0 comments on commit c674536

Please sign in to comment.