Skip to content

Commit

Permalink
allow Action reusage
Browse files Browse the repository at this point in the history
to optimise Coroutines. Extra Handles will now only be created when a second
handler is introduced on a Future, not only because the handler's ref
doesn't fit. Also, "unfitting refs" are no more at all :-)
  • Loading branch information
bergus committed Jun 24, 2016
1 parent 55c20fc commit 5d9f7cd
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 52 deletions.
2 changes: 1 addition & 1 deletion perf/doxbee-sequential/promises-creed-algebraic.js
Expand Up @@ -52,7 +52,7 @@ module.exports = function upload(stream, idOrPath, tag, done) {
}).chain(function() {
return File.whereUpdate({id: fileId}, {version: version.id})
.execWithin(tx);
}).map(function() {
}).then(function() {
tx.commit();
return done();
}, function(err) {
Expand Down
31 changes: 26 additions & 5 deletions src/Action.js
@@ -1,11 +1,26 @@
import Handle from './Handle'
import { Handle, ShareHandle } from './Handle'

export default class Action extends Handle {
constructor (promise) {
super(null)
super(null) // ref will be set when used as handle
this.promise = promise
}

_concat (action) {
if (!action._isReused() && (this._isReused() || action instanceof ShareHandle)) {
return action._concat(this)
} else {
return new ShareHandle(this.ref)._concat(this)._concat(action)
}
}
run () {
const settled = this.ref
if (this._isReused()) {
this.ref = null // make action reusable elsewhere
}
settled._runAction(this)
}

// default onFulfilled action
/* istanbul ignore next */
fulfilled (p) {
Expand All @@ -29,8 +44,14 @@ export default class Action extends Handle {
this.handle(result)
}

run () {
this.ref._runAction(this)
super.run()
tryCallContext (f, c, x) {
let result
try {
result = f.call(c, x)
} catch (e) {
this.promise._reject(e)
return
} // else
this.handle(result)
}
}
18 changes: 15 additions & 3 deletions src/Handle.js
@@ -1,15 +1,27 @@
export default class Handle {
export class Handle {
constructor (ref) {
this.ref = ref
this.length = 0
}
near () {
if (this.ref.handle !== this) {
this.ref = this.ref.near()
}
return this.ref
}
_add (action) {
// the ref will be lost, e.g. when an action is used multiple times
_isReused () {
return false // ref is stable by default
}
}

export class ShareHandle extends Handle {
constructor (ref) {
// assert: ref != null
super(ref)
this.length = 0
}
_concat (action) {
action.ref = this // a ShareHandle is not a Promise with a .handle, but .near() is enough
this[this.length++] = action
// potential for flattening the tree here
return this
Expand Down
63 changes: 26 additions & 37 deletions src/Promise.js
Expand Up @@ -6,7 +6,7 @@ import makeEmitError from './emitError'
import maybeThenable from './maybeThenable'
import { PENDING, FULFILLED, REJECTED, NEVER } from './state'
import { isNever, isSettled } from './inspect'
import Handle from './Handle'
import { ShareHandle } from './Handle'

import then from './then'
import map from './map'
Expand Down Expand Up @@ -52,7 +52,7 @@ class Core {
export class Future extends Core {
constructor () {
super()
this.handle = void 0
this.handle = void 0 // becomes something with a near() method
}

// then :: Promise e a -> (a -> b) -> Promise e b
Expand Down Expand Up @@ -134,14 +134,13 @@ export class Future extends Core {
}

_runAction (action) {
// assert: this.handle is not a Settled promise
if (this.handle) {
this.handle._add(action)
} else if (action.ref != null && action.ref !== this) {
this.handle = new Handle(this)
this.handle._add(action)
this.handle = this.handle._concat(action)
} else {
// assert: action.ref == null || action.ref.handle == action
action.ref = this
this.handle = action
this.handle.ref = this
}
}

Expand All @@ -168,30 +167,27 @@ export class Future extends Core {
}

_become (p) {
/* eslint complexity:[2,6] */
/* eslint complexity:[2,8] */
if (p === this) {
p = cycle()
}

if (this.handle) {
// assert: this.handle.ref === this
this.handle.ref = p
if (isSettled(p)) {
if (isSettled(p) || isNever(p)) {
if (this.handle) {
// assert: this.handle.ref === this
this.handle.ref = p
taskQueue.add(this.handle)
} else {
p._runAction(this.handle)
}
this.handle = p // works well because it has a near() method
} else {
if (isSettled(p)) {
// for not unnecessarily creating handles that never see any actions
// works well because it has a near() method
this.handle = p
} else if (p.handle) {
this.handle = p.handle
} else {
// explicit handle to avoid reference chain between multiple futures
this.handle = p.handle = new Handle(p)
if (this.handle) {
// assert: this.handle.ref === this
p._runAction(this.handle)
} else if (!p.handle) {
p.handle = new ShareHandle(p)
} else if (p.handle._isReused()) {
p.handle = new ShareHandle(p)._concat(p.handle)
}
this.handle = p.handle // share handle to avoid reference chain between multiple futures
}
}
}
Expand Down Expand Up @@ -245,7 +241,9 @@ class Fulfilled extends Core {
}

_when (action) {
taskQueue.add(new Continuation(action, this))
// assert: action.ref == null || action.ref === this
action.ref = this
taskQueue.add(action)
}

_runAction (action) {
Expand Down Expand Up @@ -304,7 +302,9 @@ class Rejected extends Core {
}

_when (action) {
taskQueue.add(new Continuation(action, this))
// assert: action.ref == null || action.ref === this
action.ref = this
taskQueue.add(action)
}

_runAction (action) {
Expand Down Expand Up @@ -472,14 +472,3 @@ function extractThenable (thn, thenable) {
function cycle () {
return new Rejected(new TypeError('resolution cycle'))
}

class Continuation {
constructor (action, promise) {
this.action = action
this.promise = promise
}

run () {
this.promise._runAction(this.action)
}
}
13 changes: 8 additions & 5 deletions src/coroutine.js
Expand Up @@ -13,12 +13,15 @@ class Coroutine extends Action {
constructor (resolve, iterator, promise) {
super(promise)
this.resolve = resolve
this.next = iterator.next.bind(iterator)
this.throw = iterator.throw.bind(iterator)
this.iterator = iterator
}

_isReused () {
return true
}

start () {
this.tryCall(this.next, void 0)
this.tryCallContext(this.iterator.next, this.iterator, void 0)
}

handle (result) {
Expand All @@ -30,11 +33,11 @@ class Coroutine extends Action {
}

fulfilled (ref) {
this.tryCall(this.next, ref.value)
this.tryCallContext(this.iterator.next, this.iterator, ref.value)
}

rejected (ref) {
this.tryCall(this.throw, ref.value)
this.tryCallContext(this.iterator.throw, this.iterator, ref.value)
return true
}
}
2 changes: 1 addition & 1 deletion test/lib/refcount-util.js
Expand Up @@ -2,7 +2,7 @@ import { resolve, isRejected, isNever } from '../../src/main'
import { Future } from '../../src/Promise'
import Action from '../../src/Action'

const knownNames = ['Handle', 'Action',
const knownNames = ['Handle', 'ShareHandle', 'Action',
'Then', 'Chain', 'Map', 'Delay',
'Future', 'Fulfilled', 'Rejected',
'Settle', 'Merge', 'Any', 'Race']
Expand Down

0 comments on commit 5d9f7cd

Please sign in to comment.