sca-j message
[Date Prev]
| [Thread Prev]
| [Thread Next]
| [Date Next]
--
[Date Index]
| [Thread Index]
| [List Home]
Subject: RE: [sca-j] AW: ISSUE 8: Concurrency model for Service Reference instances
- From: Mike Edwards <mike_edwards@uk.ibm.com>
- To: "OASIS Java" <sca-j@lists.oasis-open.org>
- Date: Tue, 26 Feb 2008 13:51:16 +0000
Michael,
Michael,
Oh yes, concurrent programming is ALWAYS
fun!
Comments inline....
My thinking now is that we should dispose
of the capability for a client to set the conversation ID.
Read it by all means, use it to index
some table of data if you choose, but don't try setting it !
Writing this code shows how hard it
is to get right.
Yours, Mike.
Strategist - Emerging Technologies, SCA & SDO.
Co Chair OASIS SCA Assembly TC.
IBM Hursley Park, Mail Point 146, Winchester, SO21 2JN, Great Britain.
Phone & FAX: +44-1962-818014 Mobile: +44-7802-467431
Email: mike_edwards@uk.ibm.com
"Michael Rowley"
<mrowley@bea.com>
25/02/2008 20:04
|
To
| Mike Edwards/UK/IBM@IBMGB, "OASIS
Java" <sca-j@lists.oasis-open.org>
|
cc
|
|
Subject
| RE: [sca-j] AW: ISSUE 8: Concurrency
model for Service Reference instances |
|
Mike,
I think there are two problems,
even if you made that change:
1) getConversationID() will
return null even when a conversation has already started. This is
because this function returns what the conversation ID will be for the
_next_ conversation, not for the current conversation. This
means that your code will try to set the conversation ID when the conversation
is already started (an error).
<mje> I don't follow
this at all - and I don't see this in the spec either. I think we probably
need to discuss this point on a call. </mje>
2) It also doesn’t work for
starting the conversation. I believe that two threads could pass
this check:
if (storeRef.getConversationID()
== null)
Once they’ve passed that check
there is nothing in your code which will cause one of those two threads
to give up and use the conversation ID that had been chosen by the other
thread.
<mje> true - I adjusted
the code for the double check that is in yours - I originally considered
putting in that code but thought it did not matter. I was wrong.
I've adjusted by code to
take account of this.</mje>
My isn’t this fun! :-) Actually,
I think it is useful to try to work through some of the threading issues
that our users will most likely face.
This was my attempt at the
buyBook routine. Note the double check for null and the fact that
the conversation-starting call to addToCart() is inside the synchronization
block.
<mje> Your code has
problems associated with not locking the invocation of the addToCart()
operation against the check out operation.
At the point where the
checkout() operation is called on the service reference the conversation
comes to a close and the next addToCart()
operation starts a new
one. There is a time window between calling getConversation() at
the start of your code and calling the addToCart()
operation which permits
the original conversation to be ended by another thread - this will cause
addToCart() to be called where the conversation
has ended and with no new
client ID set. This implies that the implied new conversation will
have an ID not set by the client.
My code tries to deal with
that one...
...just synchronizing the
addToCart() operation is not good, since that overthrows one of the principles
we started out with - the ability to freely
overlap the addToCart calls
and not require the client to wait locked while the addToCart() operation
completes. That is why I used a
readers / writers pattern
in my code.
</mje>
void buyBook(String ISBN)
{
if
(storeRef.getConversation() != null) {
storeRef.getService().addToCart(ISBN);
} else
{
synchronized(storeRef) {
if (storeRef.getConversation() == null) {
storeRef.setConversationID(chooseID());
}
storeRef.getService().addToCart(ISBN);
} // synchronized
}
if
(isTimeToCheckOut())
checkOut();
}
Michael
From: Mike Edwards [mailto:mike_edwards@uk.ibm.com]
Sent: Monday, February 25, 2008 1:30 PM
To: OASIS Java
Subject: RE: [sca-j] AW: ISSUE 8: Concurrency model for Service Reference
instances
Michael,
Good debugging - a single change....
---> storeRef.getConversationID()
...then I think it works fine.
Yours, Mike.
Strategist - Emerging Technologies, SCA & SDO.
Co Chair OASIS SCA Assembly TC.
IBM Hursley Park, Mail Point 146, Winchester, SO21 2JN, Great Britain.
Phone & FAX: +44-1962-818014 Mobile: +44-7802-467431
Email: mike_edwards@uk.ibm.com
"Michael Rowley"
<mrowley@bea.com>
25/02/2008 16:10
|
To
| Mike Edwards/UK/IBM@IBMGB,
"OASIS Java" <sca-j@lists.oasis-open.org>
|
cc
|
|
Subject
| RE: [sca-j] AW: ISSUE 8: Concurrency
model for Service Reference instances |
|
Mike,
Thanks, I was hoping to see code, as I think this helps to make the discussion
concrete. I have two main reactions to this:
1) It doesn’t look like you are relying on any change to the spec in this
solution. What you have can be done now, right?
2) I don’t think you solved the main race condition. I think the
window of vulnerability lasts from the time of the check getConversation==null,
all the way until the first business operation in the conversation (the
call to addToCart). Note that getConversation does not return non-null
until after the first conversational operation occurs. The call to
setConversationID(), changes the result to getConversationID(), but it
does _not_ make getConversation() return non-null immediately.
Your code lets other threads in after the call to setConversationID() and
before addToCart().
I have some proposed code in another email on this thread, which I think
might work.
Michael
From: Mike Edwards [mailto:mike_edwards@uk.ibm.com]
Sent: Sunday, February 24, 2008 8:49 AM
To: OASIS Java
Subject: RE: [sca-j] AW: ISSUE 8: Concurrency model for Service Reference
instances
Michael,
Ah - I thought I had been plain enough.
For me, the thing that needs control and protection is:
a) the start
b) the end
...of a conversation.
So in your example, this would be
1) the setting of the conversationID (which I take to be the start of a
new conversation)
2) the checkOut process (which I take to be the end of a conversation)
Now, this looks to me like a classic case of readers and writers, with
normal invocation of
the reference being a "read" and ending / starting of a conversation
being a "write" - so
I'd propose the use of ReadWriteLock mechanics from java.util.concurrent.
So I think that the
code would end up looking something like this:
@Scope(“COMPOSITE”)
class BookBatch {
@Reference ServiceReference<BookStore> storeRef;
ReeentrantReadWriteLock theLock = new ReentrantReadWriteLock();
void buyBook(String ISBN) {
if (storeRef.getConversation() == null)
startConversation();
theLock.readLock.lock();
try {
storeRef.getService().addToCart(ISBN);
} finally {
theLock.readLock.unlock();
}
if (isTimeToCheckOut())
checkOut();
}
boolean isTimeToCheckOut() {}
void setTimeToCheckOut() {}
void checkOut( ) {
theLock.writeLock().lock();
try{
storeRef.getService().checkout();
// start a new conversation
startConversation();
} finally {
theLock.writeLock.unlock();
}
}
void startConversation( ) {
theLock.writeLock().lock();
try {
// Make sure that there is no existing conversation...
if( storeRef.getConversation() == null ) {
storeRef.setConversationID(
chooseID() );
// set
the time to checkout
setTimeToCheckOut();
}
} finally {
theLock.writeLock().unlock();
}
}
String chooseID() {} // Choose a conversation ID for the next bookstore
conversation.
}
...this provides the main requirement of allowing as many threads as you
like to add books
to the order "simultaneously", while at the same time preventing
multiple threads trying to
complete conversations at the same time. It has the nice feature
of ensuring that all add
book requests are definitely within one order (add reliable messaging time
ordering of
the transmitted messages and you're bulletproof). A tweak could be
added to ensure
that a checkout only occurs once there are actually some books in the order.
The code is clearly more complex, but all synchronisation stuff will look
more complex, so
I don't think any apology is really necessary ;-)
Yours, Mike.
Strategist - Emerging Technologies, SCA & SDO.
Co Chair OASIS SCA Assembly TC.
IBM Hursley Park, Mail Point 146, Winchester, SO21 2JN, Great Britain.
Phone & FAX: +44-1962-818014 Mobile: +44-7802-467431
Email: mike_edwards@uk.ibm.com
"Michael Rowley"
<mrowley@bea.com>
22/02/2008 17:19
|
To
| Mike Edwards/UK/IBM@IBMGB,
"OASIS Java" <sca-j@lists.oasis-open.org>
|
cc
|
|
Subject
| RE: [sca-j] AW: ISSUE 8: Concurrency
model for Service Reference instances |
|
Mike,
I think I laid out a fairly believable example where a composite scoped
client (BookBatch) would deal with a conversational service (Amazon) and
then pointed out the place where threading problems might occur. I
don’t see, based on your email, how you would expect this example to be
solved.
If you don’t like the example of setting the conversation ID, then setting
a callback ID might be more to your liking. It would run into the
same race condition.
Michael
From: Mike Edwards [mailto:mike_edwards@uk.ibm.com]
Sent: Friday, February 22, 2008 6:14 AM
To: OASIS Java
Subject: Re: [sca-j] AW: ISSUE 8: Concurrency model for Service Reference
instances
Folks,
I wonder whether we're really getting to the heart of the problem here.
Let's step back and examine the whole picture for a moment.
At one level, we have a concern over the use of references by multiple
threads.
At another level we have an interest in understanding how multiple conversations
can be conducted by a client.
These interact, but not in the way that we have been discussing.
There is a third question lurking about how Callbacks work in a multi-threaded
environment. I'll get to that one later.
First, let's take a look at a simple non-conversational service and a client
with a reference to that service.
I think that the scope of the client matters little, in fact. It's
the usage of the reference that matters.
I think that a reference to a stateless service can happily be invoked
at any time by any thread belonging to
the client component. If the client has multiple threads, there may
be some concerns about the order of
requests that get sent, but that is a problem for the client to sort out.
It won't matter to the service. There
are certainly no concerns that there will be any "technical"
problems invoking the service operations.
Now, switch attention to a conversational service and the reference to
that service held by a client.
It seems to me very simple to express a principle here: a single
reference instance object represents
at most a single conversation. ( I say "at most" since
I think that we have said that is possible for such
a reference to be in "conversation not started" state and for
non conversational invocations to take
place in that state.)
Now the usage of that single reference object can be handled in one of
two ways:
1) all uses of the reference, by any thread, are for one conversation and
one conversation only
2) over time, the reference may be used for multiple conversations, but
serially - again, only one
conversation at a time
1) requires relatively little management other than ensuring only one set-up
of a conversation ID.
2) requires more control to ensure that each conversation is started and
ended in a controlled
fashion (ie setting of a conversationID, calling of an endsConversation
method etc, must be
serialized)
Let's express another principle: if a client of a conversational service
wants to conduct multiple
conversations at the same time, it must use multiple reference objects,
one for each conversation.
(This implies necessary use of the context API to fetch new copies of reference
objects)
I think that the main concern over multi-thread use of a conversational
reference is thus solved
in a relatively simple way. If you stick with the idea that one reference
is only involved in one
conversation at a time, then things are relatively simple and only a small
amount of mechanics
is required.
One comment I make is: "why do we allow the client to set the
conversationID?" What's the
purpose of this? The conversation is primarily about enabling the
same service provider
data to be reached - the conversation ID matters far more to the provider
than to the client.
I argue for removing the capability for a client to set the conversation
ID.
Now, that has dealt with conversational references. That leaves callbacks.
I'll leave those
for another email....
Yours, Mike.
Strategist - Emerging Technologies, SCA & SDO.
Co Chair OASIS SCA Assembly TC.
IBM Hursley Park, Mail Point 146, Winchester, SO21 2JN, Great Britain.
Phone & FAX: +44-1962-818014 Mobile: +44-7802-467431
Email: mike_edwards@uk.ibm.com
"Barack, Ron"
<ron.barack@sap.com>
21/02/2008 22:15
|
To
| "OASIS Java" <sca-j@lists.oasis-open.org>
|
cc
|
|
Subject
| [sca-j] AW: ISSUE 8: Concurrency model
for Service Reference instances |
|
Hi Michael,
Let me first make sure I understand your scenario. The composite-scoped
component is essentially a middle man, involved in 2 conversations, with
amazon on one side, and the customer on the other. And the problem
is to make sure that requests coming in from the client conversation get
passed to the correct amazon conversation. Both conversations can
be long running. Does that fit?
I think the scenario we had in mind was much more oriented to short lived
conversations. That is, the case where the whole conversation with
StoreRef takes place within a single call to buy books. In this case, you
simply have to replace the injected field "storeRef" with an
injected ComponentContext, and the implementation of buyBook would call
context.getService("storeRef"). Whether the component calls
storeRef.setConversationID or not, you never have any race conditions.
The situation is more complex for these long-running conversations, and
I think it's unsurprising that the code would be, too. In this case,
the code would need to map from the client conversation ID, to the ID of
the amazon conversation. That is, instead of checking if storeRef.getConversation()
is null, the code would call something like lookupStoreRefId(context.getRequestContext().getServiceReference().getConversationID()).
If the value returned was non-null, the component it would set the
storeRef.conversationID accordingly. Otherwise, it sets the conversionID
to chooseID(). The method lookupStoreRefId probably would use a DB,
but could use an in memory map, or anything else.
What I don't understand is how the alternative proposal, "P1",
would work. Are you expecting the runtime in inject storeRef's conversationID
into some thread local storage before invoking buyBooks? In this
case, isn't the implication that the runtime would be maintaining the map,
just like proposal P2 demands that the client do? Or are you assuming
that the conversationID is already on the thread from previous calls to
setConversationID? In this case, it's true that the client remains
very simple, but the solution requires
a) that the calls in the conversation always occur in the same
thread, and
b) that the server will not be restarted during the lifetime of
the conversation.
Ron
Von: Michael Rowley [mailto:mrowley@bea.com]
Gesendet: Donnerstag, 21. Februar 2008 20:45
An: OASIS Java; Barack, Ron
Betreff: ISSUE 8: Concurrency model for Service Reference instances
Here is the description of the issue 8 problem (from the PPT on today’s
call):
While the current text says that a service reference represents a single
conversation, it is not clear how a multi-threaded client should protect
a non-conversational service reference's configuration (conversation id,
callback, etc) so that it stays unmodified by other threads until an actual
invocation is executed.
Consider the following code snippet for example:
class AComponent {
@Reference ItemCheckerService srv;
void goCheckItem(long ticket, String itemId) {
ServiceReference sr = (ServiceReference) srv;
sr.setConversationID(ticket);
srv.check(itemId);
}
}
A simple synchronization may lead to strict serialization of remote calls
which is generally undesirable.
I think we should have a good idea of the likely scenarios in which this
multi-threading will happen. On today’s call, Simon suggested that
code could start its own threads. I agree this is true, but I don’t
want to concentrate on that case, since I think people who go there are
willing to be pretty sophisticated about the threading logic.
I believe other cases are that the client could be conversation or composite
scoped. Stateless and request scoped components are only active for
one thread at a time. This is implied by the semantics of the @Init
and @Destroy methods, which are called at the beginning and end of the
scope lifetime. For a stateless scope, that lifetime is one call.
For request scope, it is one remotable call (to be clarified based
on one of our open issues).
The scenario where a conversation-scoped client could be active in two
threads at once is possible, but unlikely, so I’ll concentrate on the
case where the client is composite scoped.
Consider this scenario: a composite scoped component exists for the purpose
of batching up book orders to Amazon. When orders come in to the
BookBatch component, it forwards them on to Amazon, using the shopping
cart that is associated with the current conversation. After a certain
amount of time, or a certain number of books, the current batch is purchased,
and the conversation is ended. When the next book order comes in,
a new batch (conversation) will be started. How might this look:
@Scope(“COMPOSITE”)
class BookBatch {
@Reference BookStore store;
void buyBook(String ISBN) {
store.addToCart(ISBN);
if (isTimeToCheckOut())
checkOut();
}
boolean isTimeToCheckOut() {}
void checkOut() {}
}
This seems like a potentially common scenario where the client would be
multi-threaded. Now, to run into the problem, we have to imagine
that the client wanted to choose its own conversation ID. So, perhaps
it would look like this:
@Scope(“COMPOSITE”)
class BookBatch {
@Reference ServiceReference<BookStore> storeRef;
void buyBook(String ISBN) {
if (storeRef.getConversation() == null)
storeRef.setConversationID(chooseID());
storeRef.getService().addToCart(ISBN);
if (isTimeToCheckOut())
checkOut();
}
boolean isTimeToCheckOut() {}
void checkOut() {}
String chooseID() {} // Choose a conversation ID for the next bookstore
conversation.
}
In this version, we pick a new conversation ID if a conversation isn’t
already going and set it on the service reference.
This version has a race condition! Multiple threads could have null
returned from getConversation() and so multiple threads will attempt to
choose the next conversation ID. In this particular case, it probably
doesn’t matter which one wins that race, but I suppose that in some cases
it would matter.
Is this the problem we are trying to solve? If so, I’m not sure
how the proposal in the PPT presentation given today would help much.
Ron or Simon, would you be willing to modify this class so that it works
correctly given the proposed resolution to issue 8?
Michael
Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number
741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6
3AU
Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number
741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6
3AU
Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number
741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6
3AU
Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number
741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6
3AU
[Date Prev]
| [Thread Prev]
| [Thread Next]
| [Date Next]
--
[Date Index]
| [Thread Index]
| [List Home]