A useful Promise

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

A useful Promise

Paul Baumann
A Promise object seems useful to people who discover this gem, but the change in execution sequence prevents the most useful applications of this kind of object.
 
A promise forks a process. A forked process sits idle until the active process yields to other processes. This demonstrates the execution sequence:
 
| seq promise |
seq := OrderedCollection new.
seq add: 1.
promise := [ seq add: 2. Processor activeProcess yield. seq add: 4. seq ] promise.
seq add: 3.
promise value
 OrderedCollection (1 3 2 4)
Notice how 3 is before 2? This is just a side effect of the standard way that forked processes are scheduled.
 
Now consider when you might want to use a Promise. A very typical scenario is to do some server call and allow execution to continue while that call is being being done.
 
For example, this code attempts to start a GS query that is done in parallel with opening a window. Once the window is opened then the code waits for the query results to display them.
 
| gsResultPromise |
gsResultPromise := [ GBSM execute: 'Companies getAll' ] promise.
CompaniesView new
    open;
    displayCompanies: gsResultPromise value.
 
The thing is, because of process scheduling, the query doesn't actually start until after the active process yields to the query. In other words, all this trickery is no faster, and actually slows processing by adding the overhead of another process. You tend to find that this scenario is most common. You usually want to start work that you know will be delayed, allow work to continue on the main process, and then wait for results later. Unfortunately, the standard Promise does not behave this way. The standard Promise expects work to be delayed on the main process before allowing the promised work to get started.
 
Fortunately, there is a very simple solution...
 
BlockClosure>>deferable
 "Answer a Promise after work in the receiver block is suspended. -plb 2010.04.14"
 | prom ratchetSem |
 prom := Promise new.
 ratchetSem := Semaphore new.
 ([ratchetSem signal. self value] promiseBlock: prom) fork.
 ratchetSem wait.
 ^prom
 
This one forces the Promise to give the desired sequence:

| seq promise |
seq := OrderedCollection new.
seq add: 1.
promise := [ seq add: 2. Processor activeProcess yield. seq add: 4. seq ] deferable.
seq add: 3.
promise value
 OrderedCollection (1 2 3 4)
 
Obviously, the Promise.sync semaphore could have been reused to do the ratcheting without having to create a second semaphore. I showed it with a separate ratchet semaphore just to keep the example simple.
 
Paul Baumann 
 


This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.

_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: A useful Promise

Henrik Sperre Johansen

On Apr 14, 2010, at 4:42 37PM, Paul Baumann wrote:

A Promise object seems useful to people who discover this gem, but the change in execution sequence prevents the most useful applications of this kind of object.
 
A promise forks a process. A forked process sits idle until the active process yields to other processes. This demonstrates the execution sequence:
 
| seq promise |
seq := OrderedCollection new.
seq add: 1.
promise := [ seq add: 2. Processor activeProcess yield. seq add: 4. seq ] promise.
seq add: 3.
promise value
 OrderedCollection (1 3 2 4)
Notice how 3 is before 2? This is just a side effect of the standard way that forked processes are scheduled.
 
Now consider when you might want to use a Promise. A very typical scenario is to do some server call and allow execution to continue while that call is being being done.
 
For example, this code attempts to start a GS query that is done in parallel with opening a window. Once the window is opened then the code waits for the query results to display them.
 
| gsResultPromise |
gsResultPromise := [ GBSM execute: 'Companies getAll' ] promise.
CompaniesView new
    open;
    displayCompanies: gsResultPromise value.
 
The thing is, because of process scheduling, the query doesn't actually start until after the active process yields to the query. In other words, all this trickery is no faster, and actually slows processing by adding the overhead of another process. You tend to find that this scenario is most common. You usually want to start work that you know will be delayed, allow work to continue on the main process, and then wait for results later. Unfortunately, the standard Promise does not behave this way. The standard Promise expects work to be delayed on the main process before allowing the promised work to get started.
 
Fortunately, there is a very simple solution...
 
BlockClosure>>deferable
 "Answer a Promise after work in the receiver block is suspended. -plb 2010.04.14"
 | prom ratchetSem |
 prom := Promise new.
 ratchetSem := Semaphore new.
 ([ratchetSem signal. self value] promiseBlock: prom) fork.
 ratchetSem wait.
 ^prom
 
This one forces the Promise to give the desired sequence:

| seq promise |
seq := OrderedCollection new.
seq add: 1.
promise := [ seq add: 2. Processor activeProcess yield. seq add: 4. seq ] deferable.
seq add: 3.
promise value
 OrderedCollection (1 2 3 4)
 
Obviously, the Promise.sync semaphore could have been reused to do the ratcheting without having to create a second semaphore. I showed it with a separate ratchet semaphore just to keep the example simple.
 
Paul Baumann 

In these cases, I usually do:
gsResultPromise := [GBSM execute: 'Companies getAll'] promiseAt: Processor activeProcess priority +1.

And use simple promise for the things I know won't be needed to render the main window, but would cause delays if they had to be computed when used later.

If you changed the default behavor to what you sugget, I'd have to switch these two uses around, that is promise off at a lower priority things I today use promise for, in order for them to not delay a window open operation.

So neither case is strictly "correct", it's just a matter of knowing when you have to use which.

Cheers,
Henry

_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: A useful Promise

Federico.Balaguer
In reply to this post by Paul Baumann
Paul,

This looks very interesting, I found a couple examples where
semaphores and promises are not enough but I would think that Promise
does not apply to your original example:

  | gsResultPromise |
 gsResultPromise := [ GBSM execute: 'Companies getAll' ] promise.
 CompaniesView new
     open;
     displayCompanies: gsResultPromise value.

because gsResultPromise is both the result of  the promised
blockClosure and a required part of the #displayCompanies: call.

Finally, I don't understand the benefits of #deferable given the fact
that the developer has to send #value to the instance of Promise
> | seq promise |
> seq := OrderedCollection new.
> seq add: 1.
> promise := [ seq add: 2. Processor activeProcess yield. seq add: 4. seq ]
> deferable.
> seq add: 3.
> promise value
>  OrderedCollection (1 2 3 4)

What would happen if the block looks like this? [Processor
activeProcess yield. seq add: 2. Processor activeProcess yield. seq
add: 4.   seq ]

Federico


On Wed, Apr 14, 2010 at 11:42 AM, Paul Baumann <[hidden email]> wrote:

> A Promise object seems useful to people who discover this gem, but the
> change in execution sequence prevents the most useful applications of this
> kind of object.
>
> A promise forks a process. A forked process sits idle until the active
> process yields to other processes. This demonstrates the execution sequence:
>
> | seq promise |
> seq := OrderedCollection new.
> seq add: 1.
> promise := [ seq add: 2. Processor activeProcess yield. seq add: 4. seq ]
> promise.
> seq add: 3.
> promise value
>  OrderedCollection (1 3 2 4)
> Notice how 3 is before 2? This is just a side effect of the standard way
> that forked processes are scheduled.
>
> Now consider when you might want to use a Promise. A very typical scenario
> is to do some server call and allow execution to continue while that call is
> being being done.
>
> For example, this code attempts to start a GS query that is done in parallel
> with opening a window. Once the window is opened then the code waits for the
> query results to display them.
>
> | gsResultPromise |
> gsResultPromise := [ GBSM execute: 'Companies getAll' ] promise.
> CompaniesView new
>     open;
>     displayCompanies: gsResultPromise value.
>
> The thing is, because of process scheduling, the query doesn't actually
> start until after the active process yields to the query. In other words,
> all this trickery is no faster, and actually slows processing by adding the
> overhead of another process. You tend to find that this scenario is most
> common. You usually want to start work that you know will be delayed, allow
> work to continue on the main process, and then wait for results later.
> Unfortunately, the standard Promise does not behave this way. The standard
> Promise expects work to be delayed on the main process before allowing the
> promised work to get started.
>
> Fortunately, there is a very simple solution...
>
> BlockClosure>>deferable
>  "Answer a Promise after work in the receiver block is suspended. -plb
> 2010.04.14"
>  | prom ratchetSem |
>  prom := Promise new.
>  ratchetSem := Semaphore new.
>  ([ratchetSem signal. self value] promiseBlock: prom) fork.
>  ratchetSem wait.
>  ^prom
>
> This one forces the Promise to give the desired sequence:
> | seq promise |
> seq := OrderedCollection new.
> seq add: 1.
> promise := [ seq add: 2. Processor activeProcess yield. seq add: 4. seq ]
> deferable.
> seq add: 3.
> promise value
>  OrderedCollection (1 2 3 4)
>
> Obviously, the Promise.sync semaphore could have been reused to do the
> ratcheting without having to create a second semaphore. I showed it with a
> separate ratchet semaphore just to keep the example simple.
>
> Paul Baumann
>
> ________________________________
> This message may contain confidential information and is intended for
> specific recipients unless explicitly noted otherwise. If you have reason to
> believe you are not an intended recipient of this message, please delete it
> and notify the sender. This message may not represent the opinion of
> IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and
> does not constitute a contract or guarantee. Unencrypted electronic mail is
> not secure and the recipient of this message is expected to provide
> safeguards from viruses and pursue alternate means of communication where
> privacy or a binding message is desired.
>
> _______________________________________________
> vwnc mailing list
> [hidden email]
> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>
>

_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: A useful Promise

Paul Baumann
In reply to this post by Henrik Sperre Johansen
Henry,
 
No change was suggested to the default behavior. I showed a separate protocol. Yes, knowing which to use is important.
 
| seq promise |
seq := OrderedCollection new.
seq add: 1.
promise := [ seq add: 2. Processor activeProcess yield. seq add: 4. seq ]
    promiseAt: Processor activePriority + 1.
seq add: 3.
promise value
 OrderedCollection (1 2 4 3)
The trick you show gets 2 before 3, but (in this example) it now does 4 before 3. No biggie because a real example would defer for more than just a yield. The problem I see with the trick you use is when you want the query work to be done with less importance than the active process yet you have to give it greater priority just to get it scheduled first. It is a valid approach though--as long as you don't need the background work done at equal or lower priority and as long as the active process is not already P100. Thanks for sharing.
 
Paul Baumann 
 


From: Henrik Johansen [mailto:[hidden email]]
Sent: Wednesday, April 14, 2010 11:18 AM
To: Paul Baumann
Cc: vwnc
Subject: Re: [vwnc] A useful Promise
Importance: High


On Apr 14, 2010, at 4:42 37PM, Paul Baumann wrote:

A Promise object seems useful to people who discover this gem, but the change in execution sequence prevents the most useful applications of this kind of object.
 
A promise forks a process. A forked process sits idle until the active process yields to other processes. This demonstrates the execution sequence:
 
| seq promise |
seq := OrderedCollection new.
seq add: 1.
promise := [ seq add: 2. Processor activeProcess yield. seq add: 4. seq ] promise.
seq add: 3.
promise value
 OrderedCollection (1 3 2 4)
Notice how 3 is before 2? This is just a side effect of the standard way that forked processes are scheduled.
 
Now consider when you might want to use a Promise. A very typical scenario is to do some server call and allow execution to continue while that call is being being done.
 
For example, this code attempts to start a GS query that is done in parallel with opening a window. Once the window is opened then the code waits for the query results to display them.
 
| gsResultPromise |
gsResultPromise := [ GBSM execute: 'Companies getAll' ] promise.
CompaniesView new
    open;
    displayCompanies: gsResultPromise value.
 
The thing is, because of process scheduling, the query doesn't actually start until after the active process yields to the query. In other words, all this trickery is no faster, and actually slows processing by adding the overhead of another process. You tend to find that this scenario is most common. You usually want to start work that you know will be delayed, allow work to continue on the main process, and then wait for results later. Unfortunately, the standard Promise does not behave this way. The standard Promise expects work to be delayed on the main process before allowing the promised work to get started.
 
Fortunately, there is a very simple solution...
 
BlockClosure>>deferable
 "Answer a Promise after work in the receiver block is suspended. -plb 2010.04.14"
 | prom ratchetSem |
 prom := Promise new.
 ratchetSem := Semaphore new.
 ([ratchetSem signal. self value] promiseBlock: prom) fork.
 ratchetSem wait.
 ^prom
 
This one forces the Promise to give the desired sequence:

| seq promise |
seq := OrderedCollection new.
seq add: 1.
promise := [ seq add: 2. Processor activeProcess yield. seq add: 4. seq ] deferable.
seq add: 3.
promise value
 OrderedCollection (1 2 3 4)
 
Obviously, the Promise.sync semaphore could have been reused to do the ratcheting without having to create a second semaphore. I showed it with a separate ratchet semaphore just to keep the example simple.
 
Paul Baumann 

In these cases, I usually do:
gsResultPromise := [GBSM execute: 'Companies getAll'] promiseAt: Processor activeProcess priority +1.

And use simple promise for the things I know won't be needed to render the main window, but would cause delays if they had to be computed when used later.

If you changed the default behavor to what you sugget, I'd have to switch these two uses around, that is promise off at a lower priority things I today use promise for, in order for them to not delay a window open operation.

So neither case is strictly "correct", it's just a matter of knowing when you have to use which.

Cheers,
Henry


This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.

_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
Reply | Threaded
Open this post in threaded view
|

Re: A useful Promise

Paul Baumann
In reply to this post by Federico.Balaguer
Federico,

Thanks. You are right that the example was flawed. That thrown together example opened the window in parallel, but would now allow the window to be responsive until the query finished. This is a closer approximation of code I use.

 | view gsResultPromise |
 view := CompaniesView new.
 [ view displayCompanies: (GBSM evaluate: 'Companies getAll') ] deferable.
 view open.

#displayCompanies: would include a #uiEventFor: send. The actual code shaves a second off total processing time and the windows are entirely responsive at all times. The #deferrable is different from #promise in that the query begins earlier. Obviously, if you yield before doing the work then you force the original #promise behavior back and defeat the benefit of #deferable.

Paul Baumann


-----Original Message-----
From: Federico Balaguer [mailto:[hidden email]]
Sent: Wednesday, April 14, 2010 11:21 AM
To: Paul Baumann
Cc: vwnc
Subject: Re: [vwnc] A useful Promise
Importance: High

Paul,

This looks very interesting, I found a couple examples where semaphores and promises are not enough but I would think that Promise does not apply to your original example:

  | gsResultPromise |
 gsResultPromise := [ GBSM execute: 'Companies getAll' ] promise.
 CompaniesView new
     open;
     displayCompanies: gsResultPromise value.

because gsResultPromise is both the result of  the promised blockClosure and a required part of the #displayCompanies: call.

Finally, I don't understand the benefits of #deferable given the fact that the developer has to send #value to the instance of Promise
> | seq promise |
> seq := OrderedCollection new.
> seq add: 1.
> promise := [ seq add: 2. Processor activeProcess yield. seq add: 4.
> seq ] deferable.
> seq add: 3.
> promise value
>  OrderedCollection (1 2 3 4)

What would happen if the block looks like this? [Processor activeProcess yield. seq add: 2. Processor activeProcess yield. seq
add: 4.   seq ]

Federico


On Wed, Apr 14, 2010 at 11:42 AM, Paul Baumann <[hidden email]> wrote:

> A Promise object seems useful to people who discover this gem, but the
> change in execution sequence prevents the most useful applications of
> this kind of object.
>
> A promise forks a process. A forked process sits idle until the active
> process yields to other processes. This demonstrates the execution sequence:
>
> | seq promise |
> seq := OrderedCollection new.
> seq add: 1.
> promise := [ seq add: 2. Processor activeProcess yield. seq add: 4.
> seq ] promise.
> seq add: 3.
> promise value
>  OrderedCollection (1 3 2 4)
> Notice how 3 is before 2? This is just a side effect of the standard
> way that forked processes are scheduled.
>
> Now consider when you might want to use a Promise. A very typical
> scenario is to do some server call and allow execution to continue
> while that call is being being done.
>
> For example, this code attempts to start a GS query that is done in
> parallel with opening a window. Once the window is opened then the
> code waits for the query results to display them.
>
> | gsResultPromise |
> gsResultPromise := [ GBSM execute: 'Companies getAll' ] promise.
> CompaniesView new
>     open;
>     displayCompanies: gsResultPromise value.
>
> The thing is, because of process scheduling, the query doesn't
> actually start until after the active process yields to the query. In
> other words, all this trickery is no faster, and actually slows
> processing by adding the overhead of another process. You tend to find
> that this scenario is most common. You usually want to start work that
> you know will be delayed, allow work to continue on the main process, and then wait for results later.
> Unfortunately, the standard Promise does not behave this way. The
> standard Promise expects work to be delayed on the main process before
> allowing the promised work to get started.
>
> Fortunately, there is a very simple solution...
>
> BlockClosure>>deferable
>  "Answer a Promise after work in the receiver block is suspended. -plb
> 2010.04.14"
>  | prom ratchetSem |
>  prom := Promise new.
>  ratchetSem := Semaphore new.
>  ([ratchetSem signal. self value] promiseBlock: prom) fork.
>  ratchetSem wait.
>  ^prom
>
> This one forces the Promise to give the desired sequence:
> | seq promise |
> seq := OrderedCollection new.
> seq add: 1.
> promise := [ seq add: 2. Processor activeProcess yield. seq add: 4.
> seq ] deferable.
> seq add: 3.
> promise value
>  OrderedCollection (1 2 3 4)
>
> Obviously, the Promise.sync semaphore could have been reused to do the
> ratcheting without having to create a second semaphore. I showed it
> with a separate ratchet semaphore just to keep the example simple.
>
> Paul Baumann
>
> ________________________________
> This message may contain confidential information and is intended for
> specific recipients unless explicitly noted otherwise. If you have
> reason to believe you are not an intended recipient of this message,
> please delete it and notify the sender. This message may not represent
> the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries
> or affiliates, and does not constitute a contract or guarantee.
> Unencrypted electronic mail is not secure and the recipient of this
> message is expected to provide safeguards from viruses and pursue
> alternate means of communication where privacy or a binding message is desired.
>
> _______________________________________________
> vwnc mailing list
> [hidden email]
> http://lists.cs.uiuc.edu/mailman/listinfo/vwnc
>
>


This message may contain confidential information and is intended for specific recipients unless explicitly noted otherwise. If you have reason to believe you are not an intended recipient of this message, please delete it and notify the sender. This message may not represent the opinion of IntercontinentalExchange, Inc. (ICE), its subsidiaries or affiliates, and does not constitute a contract or guarantee. Unencrypted electronic mail is not secure and the recipient of this message is expected to provide safeguards from viruses and pursue alternate means of communication where privacy or a binding message is desired.


_______________________________________________
vwnc mailing list
[hidden email]
http://lists.cs.uiuc.edu/mailman/listinfo/vwnc