The Inbox: PromisesLocal-rww.20.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

The Inbox: PromisesLocal-rww.20.mcz

commits-2
A new version of PromisesLocal was added to project The Inbox:
http://source.squeak.org/inbox/PromisesLocal-rww.20.mcz

==================== Summary ====================

Name: PromisesLocal-rww.20
Author: rww
Time: 4 October 2020, 10:43:29.453407 am
UUID: ff0cd0f9-170e-4cfb-b64a-47a1b0446903
Ancestors: PromisesLocal-rww.19

use eventual sending to handle nil blocks, both the #then: block and the #ifRejected: block

==================== Snapshot ====================

SystemOrganization addCategory: #PromisesLocal!
SystemOrganization addCategory: #'PromisesLocal-Testing'!

Object subclass: #AbstractEventual
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: AbstractEventual class>>broken: (in category 'promises') -----
broken: error
        "self broken: Error new"

        ^ BrokenEventual new
                exception: error;
                yourself!

----- Method: AbstractEventual class>>isReference: (in category 'resolution') -----
isReference: object

        ^ object class includesBehavior: AbstractEventual
!

----- Method: AbstractEventual class>>promise (in category 'promises') -----
promise
        "self promise"

        ^ self promiseInVat: self vat.
!

----- Method: AbstractEventual class>>promiseClass (in category 'promises') -----
promiseClass

        ^ Promise !

----- Method: AbstractEventual class>>promiseInVat: (in category 'promises') -----
promiseInVat: aVat
        "self promise"

        | promise buf |
        buf := OrderedCollection new: 0.
        promise := self promiseClass newOnBuffer: buf vat: aVat.
        ^ Association key: promise value: promise resolver

!

----- Method: AbstractEventual class>>refDescriptionString (in category 'accessing') -----
refDescriptionString

        ^ 'eventual'!

----- Method: AbstractEventual class>>resolution: (in category 'immediate') -----
resolution: obj

        ^ obj!

----- Method: AbstractEventual class>>resolverClass (in category 'promises') -----
resolverClass

        ^ LocalResolver !

----- Method: AbstractEventual class>>toReferenceContext: (in category 'resolution') -----
toReferenceContext: value

        ^ self toReferenceContext: value vat: self vat.
!

----- Method: AbstractEventual class>>toReferenceContext:vat: (in category 'resolution') -----
toReferenceContext: value vat: vat

        (self isReference: value)
                ifTrue: [^ value].
        (value isKindOf: Exception)
                        ifTrue: [^ AbstractEventual broken: value]
                        ifFalse: [^ NearEventual newOn: value vat: vat.].
!

----- Method: AbstractEventual>>at: (in category 'overrides') -----
at: index

        | pair |
        pair := AbstractEventual promiseInVat: self vat.
        self redirectEventualMessage: (EventualMessageSend
                message: (Message selector: #at: argument: index)
                resolver: pair value).
        ^ pair key
!

----- Method: AbstractEventual>>at:put: (in category 'overrides') -----
at: index put: value

        | pair |
        pair := AbstractEventual promiseInVat: self vat.
        self redirectEventualMessage: (EventualMessageSend
                message: (Message selector: #at:put: arguments: {index. value})
                resolver: pair value).
        ^ pair key
!

----- Method: AbstractEventual>>becomeContext: (in category 'messaging') -----
becomeContext: context

        self becomeForward: context.
!

----- Method: AbstractEventual>>doesNotUnderstand: (in category 'messaging') -----
doesNotUnderstand: aMessage

        ^ self redirectMessage: aMessage
!

----- Method: AbstractEventual>>eventual (in category 'accessing') -----
eventual

        ^ self!

----- Method: AbstractEventual>>eventualInVat: (in category 'accessing') -----
eventualInVat: aVat

        ^ self
!

----- Method: AbstractEventual>>isEventual (in category 'testing') -----
isEventual

        ^ true!

----- Method: AbstractEventual>>isFulfilled (in category 'testing') -----
isFulfilled

        ^ false!

----- Method: AbstractEventual>>isInteger (in category 'overrides') -----
isInteger

        | pair |
        pair := AbstractEventual promiseInVat: self vat.
        self redirectEventualMessage: (EventualMessageSend
                message: (Message selector: #isInteger)
                resolver: pair value).
        ^ pair key
!

----- Method: AbstractEventual>>isLocal (in category 'testing') -----
isLocal

        ^ true!

----- Method: AbstractEventual>>isNear (in category 'testing') -----
isNear

        ^ true!

----- Method: AbstractEventual>>isPassByConstruction (in category 'testing') -----
isPassByConstruction

        ^ false!

----- Method: AbstractEventual>>isPassByProxy (in category 'testing') -----
isPassByProxy

        ^ true!

----- Method: AbstractEventual>>isRejected (in category 'testing') -----
isRejected

        ^ false!

----- Method: AbstractEventual>>isRemote (in category 'testing') -----
isRemote

        ^ false!

----- Method: AbstractEventual>>isResolved (in category 'testing') -----
isResolved

        ^ false!

----- Method: AbstractEventual>>mustBeBoolean (in category 'messaging') -----
mustBeBoolean

        | context |
        context := thisContext sender.
        self resolution.
        context  skipBackBeforeJump.
        ^ true!

----- Method: AbstractEventual>>printOn: (in category 'printing') -----
printOn: stream

        stream
                nextPutAll: self class name;
                nextPutAll:  '::';
                nextPutAll: self refDescriptionString;
                nextPutAll: '('.
        self vat printOn: stream.
        stream nextPutAll: ')'.

!

----- Method: AbstractEventual>>redirectMessage: (in category 'messaging') -----
redirectMessage: aMessage

        | pair |
        pair := AbstractEventual promiseInVat: self vat.
        self redirectEventualMessage: (EventualMessageSend message: aMessage resolver: pair value).
        ^ pair key

!

----- Method: AbstractEventual>>redirectMessageOneWay: (in category 'messaging') -----
redirectMessageOneWay: aMessage

        self redirectEventualMessage: (EventualMessageSend message: aMessage).
        ^ nil
!

----- Method: AbstractEventual>>refDescriptionString (in category 'printing') -----
refDescriptionString

        ^ self class refDescriptionString
!

----- Method: AbstractEventual>>rejectWith: (in category 'Promise/A+ protocol') -----
rejectWith: reason

        self subclassResponsibility!

----- Method: AbstractEventual>>resolution (in category 'accessing') -----
resolution

        ^ self!

----- Method: AbstractEventual>>resolveWith: (in category 'Promise/A+ protocol') -----
resolveWith: arg

        self subclassResponsibility!

----- Method: AbstractEventual>>send: (in category 'eventual sending') -----
send: selector

        | pair |
        pair := AbstractEventual promiseInVat: self vat.
        self redirectEventualMessage: (EventualMessageSend selector: selector resolver: pair value).
        ^ pair key
!

----- Method: AbstractEventual>>send:args: (in category 'eventual sending') -----
send: selector args: args

        | pair |
        pair := AbstractEventual promiseInVat: self vat.
        self redirectEventualMessage: (EventualMessageSend selector: selector arguments: args resolver: pair value).
        ^ pair key
!

----- Method: AbstractEventual>>sendOneWay: (in category 'eventual sending') -----
sendOneWay: selector

        self redirectEventualMessage: (EventualMessageSend selector: selector).
        ^ nil
!

----- Method: AbstractEventual>>sendOneWay:args: (in category 'eventual sending') -----
sendOneWay: selector args: args

        self redirectEventualMessage: (EventualMessageSend selector: selector arguments: args).
        ^ nil
!

----- Method: AbstractEventual>>wait (in category 'waiting') -----
wait
        "Wait unconditionally for this promise to become fulfilled or rejected."
        PromiseWaiter waitTimeoutMSecs: 1000 onPromise: self.
        ^ self resolution.!

----- Method: AbstractEventual>>waitTimeoutMSecs: (in category 'waiting') -----
waitTimeoutMSecs: msecs
        "Wait for at most the given number of milliseconds for this promise to settle.
        Answer true if it is resolved, false otherwise. False can therefore mean EITHER 'timeout' OR 'rejected'."

        PromiseWaiter waitTimeoutMSecs: msecs onPromise: self.
        ^ self resolution.!

----- Method: AbstractEventual>>waitTimeoutSeconds: (in category 'waiting') -----
waitTimeoutSeconds: secs
        "Wait for at most the given number of milliseconds for this promise to settle.
        Answer true if it is resolved, false otherwise. False can therefore mean EITHER 'timeout' OR 'rejected'."

        PromiseWaiter waitTimeoutSeconds: secs onPromise: self.
        ^ self resolution.!

AbstractEventual subclass: #BrokenEventual
        instanceVariableNames: 'exception'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: BrokenEventual class>>refDescriptionString (in category 'accessing') -----
refDescriptionString

        ^ 'broken'
!

----- Method: BrokenEventual>>becomeContext: (in category 'messaging') -----
becomeContext: ref

        ^ self error: 'not switchable'!

----- Method: BrokenEventual>>doesNotUnderstand: (in category 'messaging') -----
doesNotUnderstand: aMessage

        ^ self exception signal!

----- Method: BrokenEventual>>error (in category 'accessing') -----
error

        ^ self exception!

----- Method: BrokenEventual>>exception (in category 'accessing') -----
exception

        ^exception!

----- Method: BrokenEventual>>exception: (in category 'accessing') -----
exception: anException

        exception := anException!

----- Method: BrokenEventual>>isBroken (in category 'testing') -----
isBroken

        ^ true!

----- Method: BrokenEventual>>isRejected (in category 'testing') -----
isRejected

        ^ true!

----- Method: BrokenEventual>>isResolved (in category 'testing') -----
isResolved

        ^ true!

----- Method: BrokenEventual>>printOn: (in category 'printing') -----
printOn: stream

        super printOn: stream.
        stream
                nextPutAll: '{';
                nextPutAll: self exception description;
                nextPutAll: '}'.
!

----- Method: BrokenEventual>>redirectEventualMessage: (in category 'messaging') -----
redirectEventualMessage: anEventualMessage

        anEventualMessage receiver: exception.
        self vat schedule: anEventualMessage.
!

----- Method: BrokenEventual>>rejectWith: (in category 'Promise/A+ protocol') -----
rejectWith: arg

        PromiseAlreadyResolved new signal.
!

----- Method: BrokenEventual>>resolution (in category 'Promise/A+ protocol') -----
resolution

        ^ exception signal!

----- Method: BrokenEventual>>resolveWith: (in category 'Promise/A+ protocol') -----
resolveWith: arg

        PromiseAlreadyResolved new signal.
!

----- Method: BrokenEventual>>value (in category 'accessing') -----
value

        ^ exception!

AbstractEventual subclass: #NearEventual
        instanceVariableNames: 'vat value'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: NearEventual class>>newOn: (in category 'instance creation') -----
newOn: anObject

        ^ self newOn: anObject vat: anObject vat.
!

----- Method: NearEventual class>>newOn:vat: (in category 'instance creation') -----
newOn: anObject vat: vat

        ^ self new
                initializeOnTarget: anObject vat: vat;
                yourself.
!

----- Method: NearEventual class>>refDescriptionString (in category 'accessing') -----
refDescriptionString

        ^ 'near'!

----- Method: NearEventual>>= (in category 'comparing') -----
= anObject
        "Answer whether the receiver and the argument represent the same
        object. If = is redefined in any subclass, consider also redefining the
        message hash."

        ^anObject = value!

----- Method: NearEventual>>basicEquality: (in category 'comparing') -----
basicEquality: anObject
        "Answer whether the receiver and the argument represent the same
        object. If = is redefined in any subclass, consider also redefining the
        message hash."

        ^value basicEquality: anObject!

----- Method: NearEventual>>becomeContext: (in category 'messaging') -----
becomeContext: context

!

----- Method: NearEventual>>hash (in category 'comparing') -----
hash

        ^value hash!

----- Method: NearEventual>>initializeOnTarget:vat: (in category 'initialization') -----
initializeOnTarget: anObject vat: aVat

        value := anObject.
        vat := aVat.
!

----- Method: NearEventual>>isFulfilled (in category 'testing') -----
isFulfilled

        ^ true!

----- Method: NearEventual>>isInteger (in category 'number protocol') -----
isInteger

        ^ value isInteger!

----- Method: NearEventual>>isPassByConstruction (in category 'serializing') -----
isPassByConstruction

        ^ value isPassByConstruction
!

----- Method: NearEventual>>isResolved (in category 'testing') -----
isResolved

        ^ true!

----- Method: NearEventual>>passByConstruction (in category 'serializing') -----
passByConstruction

        self isPassByConstruction
                ifTrue: [^ value]
                ifFalse: [self error: 'not passByConstruction'].
!

----- Method: NearEventual>>printOn: (in category 'printing') -----
printOn: stream

        stream nextPutAll: '{ '.
        value printOn: stream.
        stream nextPutAll: ' } >> '.
        super printOn: stream.

!

----- Method: NearEventual>>redirectEventualMessage: (in category 'messaging') -----
redirectEventualMessage: anEventualMessage

        anEventualMessage receiver: value.
        self vat schedule: anEventualMessage.
!

----- Method: NearEventual>>rejectWith: (in category 'Promise/A+ protocol') -----
rejectWith: arg

        PromiseAlreadyResolved new signal.
!

----- Method: NearEventual>>resolution (in category 'accessing') -----
resolution

        ^ value!

----- Method: NearEventual>>resolveWith: (in category 'Promise/A+ protocol') -----
resolveWith: arg

        PromiseAlreadyResolved new signal.
!

----- Method: NearEventual>>value (in category 'accessing') -----
value

        ^ value!

----- Method: NearEventual>>vat (in category 'accessing') -----
vat

        ^ vat!

----- Method: NearEventual>>wait (in category 'waiting') -----
wait

        super waitTimeoutMSecs: 3.
        super wait.
        ^ self resolution.!

----- Method: NearEventual>>waitTimeoutMSecs: (in category 'waiting') -----
waitTimeoutMSecs: msecs

        super waitTimeoutMSecs: 3.
        super waitTimeoutMSecs: msecs.
        ^ self resolution.!

----- Method: NearEventual>>waitTimeoutSeconds: (in category 'waiting') -----
waitTimeoutSeconds: secs

        super waitTimeoutMSecs: 3.
        super waitTimeoutSeconds: secs.
        ^ self resolution.!

AbstractEventual subclass: #Promise
        instanceVariableNames: 'vat msgBuffer resolver'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

!Promise commentStamp: 'tonyg 1/31/2018 23:34' prior: 0!
I represent the result of an asynchronous message.  Once the message is processed, I will be resolved to a value.  I am typically instantiated by invocations of #futureSend:at:args: (and not by #futureDo:atArgs:).

See class-comment of FutureNode.

I also implement the Promises/A+ Javascript specification. This allows you to chain my instances to perform arbitrarily complex asynchronous tasks with error handling baked in.

A Promise may be in one of three possible states: #pending, #fulfilled or #rejected. A Promise may move from #pending -> #fulfilled (by way of the resolveWith: message), or from #pending -> #rejected (by way of rejectWith:). No other state changes may occur.

Once #fulfilled or #rejected, a Promise's value must not change. In keeping with the major Javascript Promise implementations' interpretations of this, calls to resolveWith: or rejectWith: when a Promise is in #fulfilled or #rejected state are simply ignored - an error is not signalled. (See test cases PromiseTest testFirstResolutionWins, testCannotRejectFulfilledPromise and testCannotResolveaRejectedPromise.)!

----- Method: Promise class>>ifRejected: (in category 'instance creation') -----
ifRejected: aBlock

        ^ self new
                whenRejected: aBlock;
                yourself.
!

----- Method: Promise class>>new (in category 'instance creation') -----
new

        ^ self newOnBuffer: (OrderedCollection new: 0) vat: self vat!

----- Method: Promise class>>newOnBuffer:vat: (in category 'instance creation') -----
newOnBuffer: buf vat: vat

        ^ self basicNew
                initializeOnBuffer: buf vat: vat;
                yourself!

----- Method: Promise class>>refDescriptionString (in category 'accessing') -----
refDescriptionString

        ^ 'promise'!

----- Method: Promise class>>unit: (in category 'instance creation') -----
unit: anObject

        "Return a resolved Promise. #new is the other half of Promise's unit function; #new returns an unresolved Promise."
        ^ Promise new
                resolveWith: anObject.!

----- Method: Promise>>error (in category 'accessing') -----
error

        ^ nil!

----- Method: Promise>>fulfillWith: (in category 'Promise/A+ protocol') -----
fulfillWith: aBlock

        self fulfillWith: aBlock passErrors: (msgBuffer collect: [:e | e isRejector]) isEmpty!

----- Method: Promise>>fulfillWith:passErrors: (in category 'Promise/A+ protocol') -----
fulfillWith: aBlock passErrors: aBoolean
        "Evaluate aBlock. If it signals an exception, reject this promise with the exception
        as the argument; if it returns a value [or another Promise], resolve this promise
        with the result.
       
        If aBoolean is true, and an exception is signaled, it is passed out to the caller.
        If aBoolean is false, signaled exceptions are considered handled after the promise
        has been rejected."
        [ self resolveWith: aBlock value ]
                on: Exception
                do: [ :ex |
                        (ex isKindOf: Halt)
                                ifTrue: [ex pass]
                                ifFalse: [
                                        self rejectWith: ex.
                                        aBoolean ifTrue: [ ex pass ] ]]!

----- Method: Promise>>ifRejected: (in category 'Promise/A+ protocol') -----
ifRejected: errBlock

        ^ errBlock
                ifNil: [^ self whenBroken: [:e | e]]
                ifNotNil: [:b | self whenBroken: errBlock].
!

----- Method: Promise>>initializeOnBuffer:vat: (in category 'initialization') -----
initializeOnBuffer: buf vat: aVat

        super initialize.
        msgBuffer := buf.
        vat := aVat.
        resolver := self resolverClass onRef: self buffer: buf.
!

----- Method: Promise>>isEventual (in category 'testing') -----
isEventual

        ^ true!

----- Method: Promise>>isNear (in category 'testing') -----
isNear

        ^ false!

----- Method: Promise>>isPromise (in category 'testing') -----
isPromise

        ^ true!

----- Method: Promise>>isRejected (in category 'testing') -----
isRejected

        ^ false!

----- Method: Promise>>printOn: (in category 'printing') -----
printOn: stream

        stream
                nextPutAll: 'a Promise<';
                nextPutAll: self class name;
                nextPutAll:  '>::';
                nextPutAll: self refDescriptionString;
                nextPutAll: '('.
        self vat printOn: stream.
        stream nextPutAll: ')'.
!

----- Method: Promise>>redirectEventualMessage: (in category 'Promise/A+ protocol') -----
redirectEventualMessage: anEventualMessage

        [msgBuffer addLast: anEventualMessage]
                on: Exception
                do: [:error | self redirectEventualMessage: anEventualMessage].
!

----- Method: Promise>>reject (in category 'Promise/A+ protocol') -----
reject

        self rejectWith: nil!

----- Method: Promise>>rejectWith: (in category 'resolving') -----
rejectWith: reason

        self resolver smash: reason.!

----- Method: Promise>>resolve (in category 'Promise/A+ protocol') -----
resolve
       
        self resolveWith: nil!

----- Method: Promise>>resolveWith: (in category 'resolving') -----
resolveWith: arg
        "Resolve this promise. If arg is itself a Promise, make this promise depend upon it,
        as detailed in the Promises/A+ spec:
                https://promisesaplus.com/#the-promise-resolution-procedure"

        self resolver resolve: arg.
!

----- Method: Promise>>resolver (in category 'accessing') -----
resolver

        ^ resolver!

----- Method: Promise>>resolverClass (in category 'accessing') -----
resolverClass

        ^ self class resolverClass!

----- Method: Promise>>then: (in category 'Promise/A+ protocol') -----
then: resolvedBlock

        resolvedBlock
                ifNil: [^ self whenResolved: [:o | o]]
                ifNotNil: [:b | ^ self whenResolved: resolvedBlock].!

----- Method: Promise>>then:ifRejected: (in category 'Promise/A+ protocol') -----
then: resolvedBlock ifRejected: errBlock
        "Return a Promise that, if it resolves, runs the resolvedBlock. If resolution throws an Exception, it runs the errBlock."

        | p |
        p := self then: resolvedBlock.
        self ifRejected: errBlock.
        ^ p.!

----- Method: Promise>>value (in category 'accessing') -----
value

        ^ self!

----- Method: Promise>>vat (in category 'accessing') -----
vat

        vat isNil
                ifTrue: [vat := super vat].
        ^ vat!

----- Method: Promise>>vat: (in category 'accessing') -----
vat: aVat

        vat := aVat!

----- Method: Promise>>whenMoreResolved: (in category 'when clause') -----
whenMoreResolved: reactor

        "aBlock numArgs <= 1 ifFalse: [self error: 'Must be 0- or 1-argument block']."
        self redirectMessageOneWay: (Message selector: #whenMoreResolved: argument: reactor).
!

Object subclass: #ELib
        instanceVariableNames: ''
        classVariableNames: 'ForkDebugger'
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: ELib class>>debugEventualException: (in category 'debugging') -----
debugEventualException: anException
        "For convenience. Construct a helper process to debug an exception that occurred in the active process later on so that the active process can (try to) resume. Uses a temporary variable to access and copy the signaler context now before it gets GC'ed."

        self forkDebugger ifTrue: [
                | helperProcess |
                helperProcess := (EventualProcess
                        forContext: anException signalerContext copyStack
                        priority: Processor activeProcess priority
                        onVat: Processor activeProcess vat)
                                shouldResumeFromDebugger: true;
                                yourself.

                Project current addDeferredUIMessage: [
                        helperProcess
                                debugWithTitle: anException description
                                full: false] ].
!

----- Method: ELib class>>forkDebugger (in category 'preferences') -----
forkDebugger

        <preference: 'Fork Debugger on Exception'
                categoryList: #(promises)
                description: 'If enabled, any exception thrown inside the event loop will have its stack copied and a debugger will open outside of the event loop'
                type: #Boolean>
        ^ ForkDebugger ifNil: [false]!

----- Method: ELib class>>forkDebugger: (in category 'preferences') -----
forkDebugger: bool

        ForkDebugger := bool.
!

----- Method: Object>>basicEquality: (in category '*promiseslocal') -----
basicEquality: anObject
        "Answer whether the receiver and the argument represent the same
        object. If = is redefined in any subclass, consider also redefining the
        message hash."

        ^self == anObject!

----- Method: Object>>basicEquivalence: (in category '*promiseslocal') -----
basicEquivalence: anObject
        "Primitive. Answer whether the receiver and the argument are the same  
        object (have the same object pointer). Do not redefine the message == in  
        any other class!! Essential. No Lookup. Do not override in any subclass.  
        See Object documentation whatIsAPrimitive."


        <primitive: 110> "primitiveEquivalent"
        self primitiveFailed!

----- Method: Object>>eventual (in category '*promiseslocal') -----
eventual

        ^ NearEventual newOn: self
!

----- Method: Object>>eventualInVat: (in category '*promiseslocal') -----
eventualInVat: aVat

        ^ NearEventual newOn: self vat: aVat
!

----- Method: Object>>whenBroken: (in category '*promiseslocal') -----
whenBroken: reactor

        | pair |
        pair := AbstractEventual promiseInVat: self vat.
        self whenMoreResolved: (WhenBrokenReactor
                onClosure: reactor
                ref: self
                resolver: pair value).
        ^ pair key
!

----- Method: Object>>whenBrokenOnly: (in category '*promiseslocal') -----
whenBrokenOnly: reactor

        self whenMoreResolved: (WhenBrokenReactor
                onClosure: reactor
                ref: self
                resolver: nil).
        ^ nil
!

----- Method: Object>>whenMoreResolved: (in category '*promiseslocal') -----
whenMoreResolved: reactor

        (reactor isEventual and: [reactor isRemote])
                ifTrue: [
                        reactor redirectMessageOneWay: (Message
                                selector: #value:
                                argument: self value)]
                ifFalse: [reactor value: self value]
!

----- Method: Object>>whenRejected: (in category '*promiseslocal') -----
whenRejected: aBlock

        ^ self whenBroken: aBlock
!

----- Method: Object>>whenResolved: (in category '*promiseslocal') -----
whenResolved: reactor

        | pair |
        pair := AbstractEventual promiseInVat: self vat.
        self whenMoreResolved: (WhenResolvedReactor
                onClosure: reactor
                ref: self
                resolver: pair value).
        ^ pair key
!

----- Method: Object>>whenResolvedOnly: (in category '*promiseslocal') -----
whenResolvedOnly: reactor

        self whenMoreResolved: (WhenResolvedReactor
                onClosure: reactor
                ref: self
                resolver: nil).
        ^ nil!

Object subclass: #PriorityVat
        instanceVariableNames: 'vatNick currentState normalQ immediateQ flashQ flashOverrideQ eventualProcess accessProtect readSynch'
        classVariableNames: 'LocalVat'
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: PriorityVat class>>clearLocalVat (in category 'accessing') -----
clearLocalVat
        "PriorityVat clearLocalVat"

        ^ LocalVat ifNotNil: [LocalVat stop. LocalVat := nil]
!

----- Method: PriorityVat class>>localVat (in category 'accessing') -----
localVat
        "PriorityVat localVat"

        ^ LocalVat ifNil: [LocalVat := self newWithNick: 'local']
!

----- Method: PriorityVat class>>newWithNick: (in category 'instance creation') -----
newWithNick: nick

        ^ self new
                vatNick: nick;
                yourself!

----- Method: PriorityVat class>>stateMap (in category 'class initialization') -----
stateMap
        "(((PriorityVat stateMap compile)))"

        | desc |
        desc := ProtocolStateCompiler initialState: #running.
        (desc newState: #running -> (nil -> #stopped))
                add: #stopping -> (nil -> #stopping).
        (desc newState: #stopping -> (nil -> #stopped))
                add: #stopping -> (nil -> #stopping);
                addInteger: #stopped -> (nil -> #stopped).
        (desc newState: #stopped -> (nil -> #stopped)).
        ^desc.
!

----- Method: PriorityVat>>initialize (in category 'private') -----
initialize

        self vatNick: '<new>'.
        self start.
        currentState := self class stateMap compile.!

----- Method: PriorityVat>>isRunning (in category 'action') -----
isRunning

        ^ currentState
                ifNil: [false]
                ifNotNil: [:state | state isStateNamed: #running].!

----- Method: PriorityVat>>nextPriorityMsg (in category 'private') -----
nextPriorityMsg

        readSynch wait.
        accessProtect
                critical: [
                        flashOverrideQ isEmpty ifFalse: [ ^ flashOverrideQ next ].
                        flashQ isEmpty ifFalse: [ ^ flashQ next ].
                        immediateQ isEmpty ifFalse: [ ^ immediateQ next ].
                        normalQ isEmpty ifFalse: [ ^ normalQ next ].
                        ^ nil].!

----- Method: PriorityVat>>postCopy (in category 'private') -----
postCopy

        super postCopy.
        self initialize.!

----- Method: PriorityVat>>printOn: (in category 'private') -----
printOn: stream

        stream nextPutAll: 'vat#'.
        stream nextPutAll: self vatNick.
!

----- Method: PriorityVat>>processSends (in category 'private') -----
processSends

        [[
                Processor yield.
                self nextPriorityMsg ifNotNil: [:msg | msg value].
                self isRunning ] whileTrue]
                        ifCurtailed: [self isRunning ifTrue: [self restartEventLoop]].
       
!

----- Method: PriorityVat>>restartEventLoop (in category 'action') -----
restartEventLoop

        | currentEventLoop |
        eventualProcess ifNotNil: [:ea | currentEventLoop := ea].
        eventualProcess := nil.
        eventualProcess := EventualProcess newOnVat: self.
        eventualProcess resumeAsProcess.
        currentEventLoop ifNotNil: [:ea | ea terminate ].!

----- Method: PriorityVat>>schedule: (in category 'action') -----
schedule: msg

        self schedule: msg priority: #Normal.
!

----- Method: PriorityVat>>schedule:priority: (in category 'action') -----
schedule: msg priority: priority

        self isRunning ifFalse: [^ self].
        accessProtect critical: [
                (priority == 3 or: [priority == #FlashOverride]) ifTrue: [flashOverrideQ nextPut: msg].
                (priority == 2 or: [priority == #Flash]) ifTrue: [flashQ nextPut: msg].
                (priority == 1 or: [priority == #Immediate]) ifTrue: [immediateQ nextPut: msg].
                (priority == 0 or: [priority == #Normal]) ifTrue: [normalQ nextPut: msg]].
        readSynch signal.
!

----- Method: PriorityVat>>start (in category 'action') -----
start

        self isRunning ifTrue: [^ self].
        normalQ := SharedQueue new.
        immediateQ := SharedQueue new.
        flashQ := SharedQueue new.
        flashOverrideQ := SharedQueue new.
        accessProtect := Semaphore forMutualExclusion.
        readSynch := Semaphore new.
        eventualProcess := EventualProcess newOnVat: self.
        eventualProcess resumeAsProcess.
!

----- Method: PriorityVat>>stop (in category 'action') -----
stop

        self transitionEvent: #stopping.
        self schedule: [#cycle].
        (Delay forMilliseconds: 1) wait.
        eventualProcess ifNotNil: [:p | eventualProcess terminate].
        eventualProcess := nil.
        normalQ := nil.
        immediateQ := nil.
        flashQ := nil.
        flashOverrideQ := nil.
        accessProtect := nil.
        readSynch := nil.
!

----- Method: PriorityVat>>stopped (in category 'action') -----
stopped

        self transitionEvent: #stopped.
!

----- Method: PriorityVat>>transitionEvent: (in category 'events') -----
transitionEvent: event

        | newState |
        newState := currentState transitionEvent: event value: event client: self.
        (newState ~= currentState)
                ifTrue: [ currentState := newState. ^ true]
                ifFalse: [^ false]!

----- Method: PriorityVat>>vatNick (in category 'accessing') -----
vatNick

        ^ vatNick!

----- Method: PriorityVat>>vatNick: (in category 'accessing') -----
vatNick: nick

        vatNick := nick.
!

Object subclass: #PromiseWaiter
        instanceVariableNames: 'promise value state'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: PromiseWaiter class>>newOnPromise: (in category 'as yet unclassified') -----
newOnPromise: promise
        "Wait unconditionally for this promise to become fulfilled or rejected."

        ^ self new
                initializeOnPromise: promise;
                yourself.
!

----- Method: PromiseWaiter class>>waitOnPromise: (in category 'as yet unclassified') -----
waitOnPromise: promise
        "Wait unconditionally for this promise to become fulfilled or rejected."

        | waiter |
        waiter := PromiseWaiter newOnPromise: promise.
        ^ waiter wait.
!

----- Method: PromiseWaiter class>>waitTimeoutMSecs:onPromise: (in category 'as yet unclassified') -----
waitTimeoutMSecs: msecs onPromise: promise

        | waiter |
        waiter := PromiseWaiter newOnPromise: promise.
        ^ waiter waitTimeoutMSecs: msecs.
!

----- Method: PromiseWaiter class>>waitTimeoutSeconds:onPromise: (in category 'as yet unclassified') -----
waitTimeoutSeconds: secs onPromise: promise

        ^ self waitTimeoutMSecs: (secs * 1000) onPromise: promise!

----- Method: PromiseWaiter>>initializeOnPromise: (in category 'initialize-release') -----
initializeOnPromise: prom

        state := #pending.
        promise := prom.
        promise whenResolved: [:v | self result: v].
        promise whenRejected: [:v | self result: v].
        (promise isResolved or: [promise isRejected])
                ifTrue: [self result: promise].
!

----- Method: PromiseWaiter>>result: (in category 'accessing') -----
result: anObject
        "Set the value of result"

        (state == #fulfilled) ifTrue: [^self].
        state := #fulfilled.
!

----- Method: PromiseWaiter>>wait (in category 'control') -----
wait
        "Wait unconditionally for this promise to become fulfilled or rejected."

        | sema |
        (state == #fulfilled) ifTrue: [
                promise removeActionsWithReceiver: self.
                ^ promise value].
        sema := Semaphore new.
        promise whenResolved: [sema signal].
        [[sema wait] on: TestFailure do: [:e | ]] ensure: [promise removeActionsWithReceiver: self].
        ^ promise value.
!

----- Method: PromiseWaiter>>waitTimeoutMSecs: (in category 'control') -----
waitTimeoutMSecs: timeout

        | sema delay |
        (state == #fulfilled) ifTrue: [
                promise removeActionsWithReceiver: self.
                ^ promise resolution].
        sema := Semaphore new.
        promise whenResolved: [sema signal].
        delay := Delay timeoutSemaphore: sema afterMSecs: timeout.
        [sema wait] ensure: [
                delay unschedule.
                promise removeActionsWithReceiver: self].
        ^ promise resolution.
!

----- Method: PromiseWaiter>>waitTimeoutSeconds: (in category 'control') -----
waitTimeoutSeconds: seconds

        ^self waitTimeoutMSecs: seconds * 1000
!

Object subclass: #Reactor
        instanceVariableNames: 'ref resolver closure'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: Reactor class>>onClosure:ref:resolver: (in category 'instance creation') -----
onClosure: closure ref: ref resolver: resolver

        ^ self new
                initOnClosure: closure ref: ref resolver: resolver;
                yourself.
!

----- Method: Reactor>>initOnClosure:ref:resolver: (in category 'initialize-release') -----
initOnClosure: aClosure ref: aRef resolver: aResolver

        closure := aClosure.
        ref := aRef.
        resolver := aResolver.
!

----- Method: Reactor>>isRejector (in category 'testing') -----
isRejector

        ^ false!

----- Method: Reactor>>isResolver (in category 'testing') -----
isResolver

        ^ false!

----- Method: Reactor>>reactToLostClient: (in category 'reacting') -----
reactToLostClient: anException

        self value: anException.
        ^ nil!

----- Method: Reactor>>value: (in category 'reacting') -----
value: ignored

        closure isNil ifTrue:[^ nil].
        ^ closure  cull: ref value.
!

Reactor subclass: #WhenBrokenReactor
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: WhenBrokenReactor class>>isPassByConstruction (in category 'serialization') -----
isPassByConstruction

        ^ true!

----- Method: WhenBrokenReactor>>isRejector (in category 'testing') -----
isRejector

        ^ true!

----- Method: WhenBrokenReactor>>value: (in category 'as yet unclassified') -----
value: ignored

        | aRef aResolver aClosure result |
        closure isNil ifTrue:[^ nil].
        (ref isBroken)
                ifTrue: [
                        aRef := ref.
                        aResolver := resolver.
                        aClosure := closure.
                        ref := nil.
                        resolver := nil.
                        closure := nil.
                        [result := aClosure cull: aRef value]
                                on: Error
                                do: [:ex | result := ex].
                        aResolver notNil ifTrue: [
                                aResolver resolve: result].
                        ^ nil].
        (ref isNear)
                ifTrue: [
                        ref := nil.
                        resolver := nil.
                        closure := nil.
                        ^ nil].
        (ref isResolved)
                ifTrue: [^ nil]
                ifFalse: [ref whenMoreResolved: self.
                        ^ nil].
!

Reactor subclass: #WhenResolvedReactor
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: WhenResolvedReactor class>>isPassByConstruction (in category 'serialization') -----
isPassByConstruction

        ^ true!

----- Method: WhenResolvedReactor>>isResolver (in category 'testing') -----
isResolver

        ^ true!

----- Method: WhenResolvedReactor>>value: (in category 'as yet unclassified') -----
value: ignored

        | aRef aResolver aClosure result |
        closure isNil ifTrue:[^ nil].
        (ref isResolved)
                ifTrue: [
                        aRef := ref.
                        aResolver := resolver.
                        aClosure := closure.
                        ref := nil.
                        resolver := nil.
                        closure := nil.
                        [result := aClosure cull: aRef value]
                                on: Error
                                do: [:ex | result := ex].
                        aResolver isNil
                                ifFalse: [aResolver resolve: result].
                        ^ nil]
                ifFalse: [
                        ref whenMoreResolved: self.
                        ^ nil].
!

Object subclass: #Resolver
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

Resolver subclass: #LocalResolver
        instanceVariableNames: 'ref buf'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: LocalResolver class>>onRef:buffer: (in category 'instance creation') -----
onRef: ref buffer: buf

        ^ self new
                initializeOnRef: ref buffer: buf;
                yourself
!

----- Method: LocalResolver>>initializeOnRef:buffer: (in category 'initialize-release') -----
initializeOnRef: aRef buffer: aBuf

        ref := aRef.
        buf := aBuf.
!

----- Method: LocalResolver>>isDone (in category 'resolving') -----
isDone

        ^ ref isNil
!

----- Method: LocalResolver>>resolve: (in category 'resolving') -----
resolve: resolutionValue

        | tmp1 |
        self isDone ifTrue: [ ^ PromiseAlreadyResolved new signal ].
        tmp1 := AbstractEventual toReferenceContext: resolutionValue.
        ref becomeContext: tmp1.
        self sendMsgsToNewRef: ref.
        ref := nil.
        ^ nil
!

----- Method: LocalResolver>>sendMsgsToNewRef: (in category 'resolving') -----
sendMsgsToNewRef: newRef

        | pendingMessages msg |
        buf isNil ifTrue: [^nil].
        pendingMessages := buf readStream.
        buf := nil.
        [pendingMessages atEnd]
                whileFalse: [
                        msg := pendingMessages next.
                        newRef redirectEventualMessage: msg].
!

----- Method: LocalResolver>>smash: (in category 'resolving') -----
smash: exception

        self isDone ifTrue: [ ^ PromiseAlreadyResolved new signal ].
        ^ (AbstractEventual isReference: exception)
                ifTrue: [self resolve: exception]
                ifFalse: [
                        (exception class includesBehavior: Exception)
                                ifTrue: [self resolve: exception]
                                ifFalse: [self resolve: (BrokenEventual new
                                        exception: (BrokenPromiseValue value: exception);
                                        yourself)]].
!

----- Method: Resolver>>isDone (in category 'resolving') -----
isDone

        self subclassResponsibility !

----- Method: Resolver>>resolve: (in category 'resolving') -----
resolve: value

        self subclassResponsibility !

----- Method: Resolver>>smash: (in category 'resolving') -----
smash: exception

        self subclassResponsibility !

----- Method: Resolver>>smashString: (in category 'resolving') -----
smashString: exceptionTxt

        ^ self smash: (Error new messageText: exceptionTxt)!

ServiceProvider subclass: #PromisesLocalServiceProvider
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: PromisesLocalServiceProvider class>>initialize (in category 'initialization') -----
initialize
        ServiceRegistry current buildProvider: self new!

MessageSend subclass: #EventualMessageSend
        instanceVariableNames: 'resolver'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: EventualMessageSend class>>message: (in category 'instance creation') -----
message: aMessage

        ^ self new
                selector: aMessage selector;
                arguments: aMessage arguments;
                resolver: nil;
                yourself!

----- Method: EventualMessageSend class>>message:resolver: (in category 'instance creation') -----
message: aMessage resolver: aResolver

        ^ self new
                selector: aMessage selector;
                arguments: aMessage arguments;
                resolver: aResolver;
                yourself!

----- Method: EventualMessageSend class>>receiver:resolver:selector: (in category 'instance creation') -----
receiver: anObject resolver: aResolver selector: aSymbol
        ^ (super receiver: anObject selector: aSymbol)
                resolver: aResolver;
                yourself!

----- Method: EventualMessageSend class>>receiver:resolver:selector:arguments: (in category 'instance creation') -----
receiver: anObject resolver: aResolver selector: aSymbol arguments: anArray
        ^ (super receiver: anObject selector: aSymbol arguments: anArray)
                resolver: aResolver;
                yourself!

----- Method: EventualMessageSend class>>selector: (in category 'as yet unclassified') -----
selector: aSymbol
        ^ (super receiver: nil selector: aSymbol)
                resolver: nil;
                yourself!

----- Method: EventualMessageSend class>>selector:arguments: (in category 'as yet unclassified') -----
selector: aSymbol arguments: anArray
        ^ (super receiver: nil selector: aSymbol arguments: anArray)
                resolver: nil;
                yourself!

----- Method: EventualMessageSend class>>selector:arguments:resolver: (in category 'as yet unclassified') -----
selector: aSymbol arguments: anArray resolver: aResolver
        ^ (super receiver: nil selector: aSymbol arguments: anArray)
                resolver: aResolver;
                yourself!

----- Method: EventualMessageSend class>>selector:resolver: (in category 'as yet unclassified') -----
selector: aSymbol resolver: aResolver
        ^ (super receiver: nil selector: aSymbol)
                resolver: aResolver;
                yourself!

----- Method: EventualMessageSend>>isOneWay (in category 'testing') -----
isOneWay

        ^ self resolver isNil!

----- Method: EventualMessageSend>>isRejector (in category 'testing') -----
isRejector

        ^ (self selector == #whenMoreResolved:)
                and: [self arguments first isRejector]!

----- Method: EventualMessageSend>>printOn: (in category 'private') -----
printOn: t1
        | t2 |
        t1 nextPutAll: 'EventualSend ('.
        receiver printOn: t1.
        t1 nextPutAll: ' '.
        (selector isUnary
                        or: [selector isInfix])
                ifTrue: [selector printOn: t1.
                        t1 nextPutAll: ' '.
                        self arguments
                                do: [:t3 | t1 print: t3]
                                separatedBy: [t1 nextPutAll: ' ']]
                ifFalse: [t2 := (self selector subStrings: ':')
                                                collect: [:t3 | t3 asSymbol asSimpleSetter asString].
                        t2
                                with: self arguments
                                do: [:t3 :t4 |
                                        t1 nextPutAll: ' ';
                                                 nextPutAll: t3;
                                                 nextPutAll: ' '.
                                        t4 printOn: t1]].
        t1 nextPutAll: ') -> ['.
        resolver printOn: t1.
        t1 nextPutAll: ']'!

----- Method: EventualMessageSend>>resolve: (in category 'api') -----
resolve: ref

        self resolver ifNotNil: [self resolver resolve: ref].!

----- Method: EventualMessageSend>>resolver (in category 'accessing') -----
resolver

        ^ resolver!

----- Method: EventualMessageSend>>resolver: (in category 'accessing') -----
resolver: aResolver

        resolver := aResolver!

----- Method: EventualMessageSend>>smash: (in category 'api') -----
smash: exception

        self resolver ifNotNil: [self resolver smash: exception].!

----- Method: EventualMessageSend>>value (in category 'api') -----
value

        | value |
        [
                value := receiver
                        perform: selector
                        withArguments: (self collectArguments: arguments)
                        inSuperclass: receiver class.
                self resolver notNil ifTrue: [ self resolver resolve: value ]
        ]
                on: Exception
                do: [:ex |
                        self resolver ifNotNil: [:r | r smash: ex].
                        (ex isKindOf: Halt)
                                ifTrue: [ex pass]
                                ifFalse: [ELib debugEventualException: ex]].

!

Process subclass: #EventualProcess
        instanceVariableNames: 'eventualName vat'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: EventualProcess class>>forContext:priority:onVat: (in category 'instance creation') -----
forContext: aContext priority: anInteger onVat: aVat
        "Answer an instance of me that has suspended aContext at priority
        anInteger."

        | newProcess |
        newProcess := self newOnVat: aVat.
        newProcess suspendedContext: aContext.
        newProcess priority: anInteger.
        ^newProcess!

----- Method: EventualProcess class>>newOnVat: (in category 'instance creation') -----
newOnVat: vat
        ^ super new
                priority: Processor userBackgroundPriority;
                eventualName: 'vat thread';
                vat: vat;
                setupContext;
                yourself!

----- Method: EventualProcess>>eventualName (in category 'accessing') -----
eventualName

        ^ eventualName!

----- Method: EventualProcess>>eventualName: (in category 'accessing') -----
eventualName: aName

        eventualName := aName.
!

----- Method: EventualProcess>>printOn: (in category 'accessing') -----
printOn: aStream

        aStream nextPutAll: '{squeake'.
        self eventualName notNil
                ifTrue: [
                        aStream nextPutAll: '-'.
                        aStream nextPutAll: self eventualName asString].
        aStream nextPutAll: '} '.
        super printOn: aStream.
!

----- Method: EventualProcess>>resume (in category 'changing process state') -----
resume

        (Processor activeProcess == self)
                ifTrue: [self resumeAsProcess]
                ifFalse: [self resumeInVat].
!

----- Method: EventualProcess>>resumeAsProcess (in category 'changing process state') -----
resumeAsProcess

        super resume.!

----- Method: EventualProcess>>resumeInVat (in category 'changing process state') -----
resumeInVat

        self vat schedule: self suspendedContext.!

----- Method: EventualProcess>>setupContext (in category 'initialize-release') -----
setupContext

        self suspendedContext: [
                self vat processSends.
                Processor terminateActive] asContext.!

----- Method: EventualProcess>>vat (in category 'accessing') -----
vat

        ^ vat!

----- Method: EventualProcess>>vat: (in category 'accessing') -----
vat: aVat

        vat := aVat!

----- Method: Process>>vat (in category '*promiseslocal') -----
vat

        ^ PriorityVat localVat.!

----- Method: BlockClosure>>eventual (in category '*promiseslocal') -----
eventual

        | pair eMsg |
        pair := AbstractEventual promiseInVat: self vat.
        eMsg := EventualMessageSend receiver: self resolver: pair value selector: #value.
        self vat schedule: eMsg.
        ^ pair key

!

----- Method: ProtoObject>>basicEquivalence: (in category '*promiseslocal') -----
basicEquivalence: anObject
        "Primitive. Answer whether the receiver and the argument are the same
        object (have the same object pointer). Do not redefine the message == in
        any other class!! Essential. No Lookup. Do not override in any subclass.
        See Object documentation whatIsAPrimitive."

        <primitive: 110>
        self primitiveFailed!

----- Method: ProtoObject>>defaultLabelForInspector (in category '*promiseslocal') -----
defaultLabelForInspector
        "Answer the default label to be used for an Inspector window on the receiver."

        ^ self class name!

----- Method: ProtoObject>>inspect (in category '*promiseslocal') -----
inspect
        "Create and schedule an Inspector in which the user can examine the receiver's variables."

        Inspector openOn: self withEvalPane: true!

----- Method: ProtoObject>>isBroken (in category '*promiseslocal') -----
isBroken

        ^ false!

----- Method: ProtoObject>>isEventual (in category '*promiseslocal') -----
isEventual

        ^ false
!

----- Method: ProtoObject>>isFulfilled (in category '*promiseslocal') -----
isFulfilled

        ^ self isNear
!

----- Method: ProtoObject>>isNear (in category '*promiseslocal') -----
isNear

        ^ true!

----- Method: ProtoObject>>isPromise (in category '*promiseslocal') -----
isPromise

        ^ false
!

----- Method: ProtoObject>>isProxy (in category '*promiseslocal') -----
isProxy

        ^ false
!

----- Method: ProtoObject>>isResolved (in category '*promiseslocal') -----
isResolved

        ^ self isPromise not
!

----- Method: ProtoObject>>redirectEventualMessage: (in category '*promiseslocal') -----
redirectEventualMessage: anEventualMessage

        anEventualMessage receiver: self.
        self vat schedule: anEventualMessage.
!

----- Method: ProtoObject>>resolution (in category '*promiseslocal') -----
resolution

        ^ self!

----- Method: ProtoObject>>vat (in category '*promiseslocal') -----
vat

        ^ Processor activeProcess vat
!

TestCase subclass: #PriorityVatTest
        instanceVariableNames: 'eventReceived'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal-Testing'!

----- Method: PriorityVatTest>>tearDown (in category 'initialize-release') -----
tearDown

        (PriorityVat clearLocalVat; localVat) restartEventLoop.
!

----- Method: PriorityVatTest>>testPriorityVat (in category 'testing') -----
testPriorityVat

        | vat |
        vat := PriorityVat newWithNick: 'testVat'.
        eventReceived := false.
        vat schedule: [eventReceived := true].
        (Delay forMilliseconds: 1) wait.
        self assert: eventReceived.
        vat stop.!

TestCase subclass: #RefsTest
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal-Testing'!

----- Method: RefsTest>>tearDown (in category 'initialize-release') -----
tearDown

        (PriorityVat clearLocalVat; localVat) restartEventLoop.
!

----- Method: RefsTest>>testArithmeticPrimitivesWithPromiseReceiver (in category 'testing') -----
testArithmeticPrimitivesWithPromiseReceiver
        | t1 t2 |
        t1 := 42 eventual.
        t2 := 10.
        t1 * t2
                whenResolved: [:t3 |
                        self assert: t3 resolution == 420]!

----- Method: RefsTest>>testBasicPromiseResolution (in category 'testing') -----
testBasicPromiseResolution

        | pair |
        pair := AbstractEventual promise.
        self assert: pair key isEventual.
        pair value resolve: 'rob'.
        self assert: pair key isNear.
        self assert: pair key = 'rob'.
!

----- Method: RefsTest>>testBasicPromiseSmashing (in category 'testing') -----
testBasicPromiseSmashing
        | pair |
        pair := AbstractEventual promise.
        self assert: pair key isEventual.
        pair value smashString: 'test promise smashing'.
        self assert: pair key isBroken.
        self
                should: [pair key immediate]
                raise: Error!

----- Method: RefsTest>>testBasicPromiseToBooleanResolution (in category 'testing') -----
testBasicPromiseToBooleanResolution
        | tmp1 |
        tmp1 := AbstractEventual promise.
        self assert: tmp1 key isEventual.
        tmp1 value resolve: true.
        self
                assert: tmp1 key isNear;
                assert: tmp1 key = true!

----- Method: RefsTest>>testBlockClosure (in category 'testing') -----
testBlockClosure

        | result |
        [42 * 10] eventual whenResolved: [:r | result := r resolution].
        (Delay forMilliseconds: 100) wait.
        self assert: result = 420.!

----- Method: RefsTest>>testFailureArithmeticPrimitivesWithPromiseArgument (in category 'testing') -----
testFailureArithmeticPrimitivesWithPromiseArgument

        | num1 num2 |
        num1 := 10.
        num2 := 42 eventual.
        [num1 * num2
                whenResolved: [:result | self assert: result == 420].
        self assert: false]
                        on: Exception do: [:ex | ^ self assert: true].
        self assert: false!

----- Method: RefsTest>>testLocalResolve (in category 'testing') -----
testLocalResolve

        | pair |
        pair := AbstractEventual promise.
        pair value resolve: 'rob'.
        self assert: (pair key = 'rob').
        self should: [pair value smashString: 'test smash'] raise: Error.
!

----- Method: RefsTest>>testLocalSmash (in category 'testing') -----
testLocalSmash
        | pair |
        pair := AbstractEventual promise.
        pair value smashString: 'smash promise test'.
        self
                should: [pair key foobar]
                raise: Error.
        self
                should: [pair value smashString: 'smash promise test']
                raise: Error!

----- Method: RefsTest>>testNearRefs (in category 'testing') -----
testNearRefs
        | obj |
        obj := Object new.
        self assert: (obj eventual = obj eventual).
!

----- Method: RefsTest>>testUsedLocalResolver (in category 'testing') -----
testUsedLocalResolver

        | resolver |
        resolver := LocalResolver onRef: nil buffer: nil.
        self assert: resolver isDone.
        self should: [resolver resolve: 2] raise: Error.
        self should: [resolver smashString: 'test smash'] raise: Error.
!

----- Method: RefsTest>>testWhenBroken (in category 'testing') -----
testWhenBroken

        | result oldForkDebugger |
        oldForkDebugger := ELib forkDebugger.
        ELib forkDebugger: false.
        42 eventual / 0
                whenBroken: [:t3 |
                        result := t3 resolution.
                        self assert: result isError].
        (Delay forMilliseconds: 100) wait.
        self assert: (result class == ZeroDivide).
        ELib forkDebugger: oldForkDebugger.
!

----- Method: RefsTest>>testWhenResolved (in category 'testing') -----
testWhenResolved

        | result  t1 t2 |
        t1 := 42 eventual.
        t2 := 10.
        t1 * t2
                whenResolved: [:t3 |
                        result := t3 resolution.
                        self assert: result == 420].
        (Delay forMilliseconds: 100) wait.
        self assert: result == 420.!

Error subclass: #BrokenPromise
        instanceVariableNames: 'exception'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

!BrokenPromise commentStamp: 'tonyg 2/17/2017 13:53' prior: 0!
I am signalled when, during a Promise>>wait, the promise is rejected.
        promise: the promise itself.
!

----- Method: BrokenPromise>>defaultAction (in category 'as yet unclassified') -----
defaultAction
        self messageText: 'Promise was rejected'.
        ^super defaultAction!

----- Method: BrokenPromise>>exception (in category 'as yet unclassified') -----
exception
        ^ exception!

----- Method: BrokenPromise>>exception: (in category 'as yet unclassified') -----
exception: aX
        exception := aX!

----- Method: BrokenPromise>>isResumable (in category 'as yet unclassified') -----
isResumable
        ^ true!

----- Method: BrokenPromise>>promise (in category 'as yet unclassified') -----
promise
        ^ self exception!

Error subclass: #BrokenPromiseValue
        instanceVariableNames: 'value'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!

----- Method: BrokenPromiseValue class>>value: (in category 'instance creation') -----
value: obj

        ^ self new
                value: obj;
                yourself!

----- Method: BrokenPromiseValue>>defaultAction (in category 'handling') -----
defaultAction

        self messageText: 'Promise was rejected with value: ', value.
        ^super defaultAction!

----- Method: BrokenPromiseValue>>isResumable (in category 'handling') -----
isResumable

        ^ true!

----- Method: BrokenPromiseValue>>value (in category 'accessing') -----
value

        ^ value.!

----- Method: BrokenPromiseValue>>value: (in category 'accessing') -----
value: anObject

        value := anObject.!

Error subclass: #PromiseAlreadyResolved
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'PromisesLocal'!