You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tomcat.apache.org by Justin Ruthenbeck <ju...@nextengine.com> on 2003/07/02 02:19:45 UTC

RE: How to synchronize based on session? (Prevent multiple submissions of forms)

At 04:39 PM 7/1/2003, you wrote:
>IMHO, Justin's proposal will not work since the servlet container may choose
>to pool multiple instances of the same servlet class and assign an incoming
>request to an available instance.

Agreed. If the container chooses to pool multiple instances of the same 
servlet (I'm assuming this can be the case with Tomcat?), then you're right 
-- it won't work.

>Things get even worse in a distributed
>environment involving multiple tomcats running in different jvms.
>Because these requests are handled by multiple threads and maybe even
>multiple servlet instances, the one thing in common to all those requests is
>the session object. IMO, a session attribute is the preferable object to
>sync.

(The above was spliced together)
If you're in a distributed environment, then synching on a session 
attribute Object won't work either -- different JVMs have different copies 
of the same object.  The problem is that *any* synchronization gets worse 
when you're in a distributed environment.  If this is the case, then 
nothing on the servlet level can *GUARANTEE* a lock -- enter transactions.

>I am sticking to my earlier post suggesting to implement your own
>HttpSessionListener. Since you are talking about an order page the session
>has been created some pages before (e.g. on login), right? If the user hits
>the submit button multiple times this will result in the corresponding
>number of http requests each containing the cookie header with the one
>unique jsessionid established before. There is no need to worry about
>creating multiple sessions.

This is getting theoretical, but assuming you have the following in your 
service() method:

1: Boolean isInProcess = session.getAttribute(IN_PROCESS);
2: if (isInProcess.booleanValue() == false)
3: {
4:    session.setAttribute(IN_PROCESS, TRUE);
4:    [PROCESS THE DATA]
5: }

How would you guarantee that Thread 1 doesn't get interrupted after 
processing Line1 by Thread 2 who processes Line1 before returning 
control?  If this happens, both threads can get isInProcess == false and 
will process the data.  Or, if you can avoid it by using a synch block, how 
would you do so (keeping in mind this may be in a distributed environment)?

I don't see how this is possible without a third resource (such as a 
database or other tx manager), but would like to know if you can come up 
with something.

justin

> > -----Original Message-----
> > From: Justin Ruthenbeck [mailto:justinr@nextengine.com]
> > Sent: Wednesday, July 02, 2003 12:48 AM
> > To: Tomcat Users List
> > Subject: RE: How to synchronize based on session? (Prevent
> > multiple submissions of forms)
> >
> >
> >
> >  > // Member variable
> >  > protected Object m_synchObj = new Object();
> >
> > Yes, your service() method will be run multi-threaded, but
> > all of those
> > threads share the same *servlet* member variables (declared
> > outside your
> > service() method).  They do not, as you asked, each have
> > their own copy of
> > that variable.  If you synch on an object created there, all request
> > threads running your servlet will wait on a lock for that object.
> >
> > It's very possible that Tomcat may already synch on session
> > creation, I
> > don't know (and I can't look at the source from where I'm at now) ...
> > perhaps someone else out there knows or can check?
> >
> > As I said, if I wanted to make ABSOLUTELY sure that no two
> > threads had your
> > problem, I would feel comfortable synching like this unless
> > the number of
> > concurrent requests was very, very, high (which would be very
> > rare).  If
> > Tomcat already synchs, then I would look into using an
> > HttpSessionListener
> > (Disclaimer: I haven't fully thought through that option yet).
> >
> > justin
> >
> >
> > At 03:35 PM 7/1/2003, you wrote:
> > >Hello Justin,
> > >
> > >Thanks for your response.  My understanding is that there will be
> > >multiple instances of a servlet depending on what the
> > container thinks
> > >is necessary.  This will most likely happen, because this is
> > a high-volume
> > >site.  If that's the case, won't there be multiple instances of the
> > >servlet, each with its own member variable?  I believe this
> > would lead
> > >for varying results as a particular session could access any of these
> > >instances on a given request.
> > >
> > >Now granted, what you've mentioned below will make the race
> > condition even
> > >harder to meet, since we've now introduced a level of synchronization
> > >based on the servlets themselves, but I believe it still has
> > the potential
> > >to be a problem... since two different instances of the
> > servlet could both
> > >attempt to add the session attribute at exactly the same time... even
> > >though each has its own respective lock.
> > >
> > >As you've probably guessed, we've been avoiding the single-threaded
> > >servlet model, because we do have such a large volume of
> > >simultaneous accesses on these pages.
> > >
> > >Thanks again,
> > >-Raiden
> > >
> > >
> > >On Tue, 1 Jul 2003, Justin Ruthenbeck wrote:
> > >
> > > >
> > > > If your code worked like this:
> > > >
> > > > 1:   if (session.getAttribute(LOCKED_ATTR) == LOCKED)
> > > > 2:      return user a waiting page
> > > > 3:   else
> > > > 4:   {
> > > > 5:      session.setAttribute(LOCKED_ATTR, LOCKED)
> > > > 6:      work with submitted data
> > > > 7:   }
> > > >
> > > > then you're right -- it's possible for one thread to get
> > interrupted after
> > > > evaluating line 1 to "false".  Another thread could come
> > in, evaluate line
> > > > 1 to "false" again and you'd end up with two threads
> > doing the "work with
> > > > submitted data" code (a double submission).
> > > >
> > > > I don't see any way around this save for synchronizing on
> > a lock in your
> > > > servlet.  Don't worry about setting anything at Session
> > creation time ...
> > > > make your logic do something like the following:
> > > >
> > > > // Member variable
> > > > protected Object m_synchObj = new Object();
> > > >
> > > > // In your service() method
> > > > boolean processData = false;
> > > > synchronized (m_synchObj)
> > > > {
> > > >     if (session.getAttribute(LOCKED_ATTR) != LOCKED)
> > > >     {
> > > >        session.setAttribute(LOCKED_ATTR, LOCKED);
> > > >        processData = true;
> > > >     }
> > > > }
> > > > if (processData)
> > > > {
> > > >     [Process the Data]
> > > > }
> > > > else
> > > > {
> > > >     [Send user to a "waiting" page]
> > > > }
> > > >
> > > > True, you've now got a potential bottleneck in your
> > servlet since every
> > > > thread for that servlet waits (a small amount of time) on a single
> > > > object.  Unless you've got *LOTS* of concurrent servlet
> > threads, I don't
> > > > think this would affect you.  I haven't given due
> > diligence to the thought
> > > > of synching on an Object in the session, but perhaps
> > that's a better idea
> > > > -- seems like you could still have a race condition if
> > two thread both
> > > > tried to create a session at the same time (perhaps
> > Tomcat guarantees this
> > > > won't happen by synching in it's session creation code?).
> >  You could
> > > > probably work something out with an HttpSessionListener.
> > Me?  I'd synch on
> > > > the servlet in favor of simplifying my code.
> > > >
> > > > justin
> > > >
> > > >
> > > > At 02:56 PM 7/1/2003, you wrote:
> > > > >Hi Stefan,
> > > > >
> > > > >Thanks for the explanation.  That makes sense, except
> > how can you make
> > > > >sure that the object is added to the session only once,
> > when the session
> > > > >is created?
> > > > >
> > > > >It would seem like you would need to synchronize on
> > something else for
> > > > >adding the object to the session, because there might be a race
> > > > >condition (unless there is a special bit of code that is
> > only ever called
> > > > >once, when the session is created).  Otherwise, two
> > different threads
> > > > >might each have their own instance of the lock object.
> > > > >
> > > > >How do I guarantee that the lock object is only created
> > once, at the time
> > > > >the session is created?
> > > > >
> > > > >Thanks,
> > > > >Raiden
> > > > >
> > > > >
> > > > >On Tue, 1 Jul 2003, Stefan Radzom wrote:
> > > > >
> > > > > > For security reasons your are not handed the internal
> > > > > > org.apache.catalina.session.StandardSession .
> > Instead, the implicit
> > > session
> > > > > > you are trying to synchronize holds a reference to
> > > > > > org.apache.catalina.session.StandardSessionFacade
> > which is backed
> > > by the
> > > > > > actual StandardSession to which all method calls are
> > delegated. The
> > > facade
> > > > > > object can therefore change between different
> > requests, thus making
> > > it not
> > > > > > appropriate for synchronisation.
> > > > > >
> > > > > > Try putting a dummy object into the session when
> > creating a new
> > > session:
> > > > > > session.setAttribute("lock", new Object()) . On
> > subsequent requests
> > > you can
> > > > > > get the attribute and obtain a lock on this object.
> > > > > >
> > > > > > Object lock = session.getAttribute("lock");
> > > > > > synchronized (lock) { ; }
> > > > > >
> > > > > > -Stefan
> > > > > >
> > > > > >
> > > > > > > -----Original Message-----
> > > > > > > From: Raiden [mailto:raiden@wonko.inow.com]
> > > > > > > Sent: Tuesday, July 01, 2003 8:19 PM
> > > > > > > To: tomcat-user@jakarta.apache.org
> > > > > > > Subject: How to synchronize based on session? (Prevent
> > > > > > > multiple submissions of forms)
> > > > > > >
> > > > > > >
> > > > > > > Hello,
> > > > > > >
> > > > > > > We are trying to prevent the "multiple submission
> > of a form problem",
> > > > > > > that can result when a user double-clicks the submit button.
> > > > > > > We have a
> > > > > > > process in place, but we have been unable to get
> > the session based
> > > > > > > synchronization to work correctly.
> > > > > > >
> > > > > > > Our order page submits to a "meta-refresh" page
> > that checks to
> > > see the
> > > > > > > current status of the order processing each time it loads.
> > > > > > > If there is
> > > > > > > an error, it sends the user back to the order page.
> >  If the order was
> > > > > > > successful, the user is sent to the success page.  If the
> > > > > > > order is still
> > > > > > > processing, it refreshes in 5 seconds (and
> > meanwhile, the user has a
> > > > > > > pretty 5 second progress bar to watch).  However, The
> > > > > > > relevant snippet of
> > > > > > > code is below:
> > > > > > >
> > > > > > > String nextUrl = null;
> > > > > > > synchronized (session) {
> > > > > > >   BigDecimal status =
> > > > > > >
> > (BigDecimal)session.getAttribute(SiteProps.PROCESS_STATUS);
> > > > > > >
> > > > > > >   if (status != null) {
> > > > > > >     System.out.println("  Processing has already
> > begun.  Checking
> > > > > > > status.");
> > > > > > >
> > > > > > >     if (status.equals(SiteProps.PROCESS_SUCCESS)) {
> > > > > > >       System.out.println("  We already know it was approved.
> > > > > > > Just showing
> > > > > > > them the success page.");
> > > > > > >       nextUrl = "/success.jsp";
> > > > > > >
> > response.sendRedirect(response.encodeRedirectUrl(nextUrl));
> > > > > > >       return;  // will remove status object on next page
> > > > > > >     } else if (status.equals(SiteProps.PROCESS_ERROR)) {
> > > > > > >       System.out.println("  We know the credit card charge
> > > > > > > failed.  Send
> > > > > > > them back to the order form.");
> > > > > > >       nextUrl = "/order.jsp";
> > > > > > >
> > response.sendRedirect(response.encodeRedirectUrl(nextUrl));
> > > > > > >       return;  // will remove status object on next page
> > > > > > >     } else if (status.equals(SiteProps.PROCESS_UNKNOWN)) {
> > > > > > >       System.out.println("  Still processing.  Not sure what
> > > > > > > the result
> > > > > > > will be.  This page will refresh in 5 seconds.");
> > > > > > >       nextUrl = "/order_check.jsp"; // refresh back
> > to this same page
> > > > > > >     }
> > > > > > >   } else {
> > > > > > >     // we don't have any record of a previous
> > attempt to process
> > > > > > >     System.out.println("  First time trying to
> > process order page.
> > > > > > > Starting processing, scheduling a refresh.");
> > > > > > >     session.setAttribute(SiteProps.PROCESS_STATUS,
> > > > > > >       SiteProps.PROCESS_UNKNOWN);
> > > > > > >   }
> > > > > > >   %>
> > > > > > >   <html>
> > > > > > >     <!-- progress bar animated gif html code here -->
> > > > > > >   </html>
> > > > > > >   <%
> > > > > > >   if (nextUrl == null) {
> > > > > > >     // first time to page
> > > > > > >
> > > > > > >     // order processing code here
> > > > > > >   }
> > > > > > > }
> > > > > > >
> > > > > > > The problem is, that there isn't any real synchronization
> > > > > > > here.  This is
> > > > > > > because the "session" object is a different object
> > each time this
> > > page
> > > > > > > loads.  In fact, printing out the "session" object
> > each time the page
> > > > > > > loads results in:
> > > > > > >
> > > > > > > org.apache.catalina.session.StandardSessionFacade@24ea85
> > > > > > > org.apache.catalina.session.StandardSessionFacade@10275fa
> > > > > > > org.apache.catalina.session.StandardSessionFacade@18706f6
> > > > > > > org.apache.catalina.session.StandardSessionFacade@f1f34a
> > > > > > > org.apache.catalina.session.StandardSessionFacade@fb6763
> > > > > > >
> > > > > > > Since the session object keeps changing, what is
> > the proper way to
> > > > > > > synchronize based on the user's session, to make this
> > > > > > > anti-double-process
> > > > > > > code work properly?
> > > > > > >
> > > > > > > This middle page with the progress bar helps to
> > remove most of our
> > > > > > > double-click problems, but occassionally a user is able to
> > > > > > > get multiple
> > > > > > > copies of this "progress page" opened, and then if
> > the timing
> > > > > > > is right,
> > > > > > > they both enter that snippet of code above at the same time,
> > > > > > > both think
> > > > > > > it's the first time this page is being processed, and both
> > > > > > > run the order
> > > > > > > processing code.
> > > > > > >
> > > > > > > Thank you,
> > > > > > > Raiden
> > > > > > >
> > > > > > >
> > > > > > >
> > ---------------------------------------------------------------------
> > > > > > > To unsubscribe, e-mail:
> > tomcat-user-unsubscribe@jakarta.apache.org
> > > > > > > For additional commands, e-mail:
> > tomcat-user-help@jakarta.apache.org
> > > > > > >
> > > > > > >
> > > > > >
> > > > > >
> > > > > >
> > > > > >
> > ---------------------------------------------------------------------
> > > > > > To unsubscribe, e-mail:
> > tomcat-user-unsubscribe@jakarta.apache.org
> > > > > > For additional commands, e-mail:
> > tomcat-user-help@jakarta.apache.org
> > > > > >
> > > > >
> > > >
> > >---------------------------------------------------------------------
> > > > >To unsubscribe, e-mail:
> > tomcat-user-unsubscribe@jakarta.apache.org
> > > > >For additional commands, e-mail:
> > tomcat-user-help@jakarta.apache.org
> > > >
> > > >
> > > > ____________________________________
> > > > Justin Ruthenbeck
> > > > Software Engineer, NextEngine Inc.
> > > > justinr - AT - nextengine DOT com
> > > > Confidential
> > > >     See http://www.nextengine.com/confidentiality.php
> > > > ____________________________________
> > > >
> > > >
> > > >
> > ---------------------------------------------------------------------
> > > > To unsubscribe, e-mail: tomcat-user-unsubscribe@jakarta.apache.org
> > > > For additional commands, e-mail:
> > tomcat-user-help@jakarta.apache.org
> > > >
> > >
> > >---------------------------------------------------------------------
> > >To unsubscribe, e-mail: tomcat-user-unsubscribe@jakarta.apache.org
> > >For additional commands, e-mail: tomcat-user-help@jakarta.apache.org
> >
> >
> > ____________________________________
> > Justin Ruthenbeck
> > Software Engineer, NextEngine Inc.
> > justinr - AT - nextengine DOT com
> > Confidential
> >     See http://www.nextengine.com/confidentiality.php
> > ____________________________________
> >
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: tomcat-user-unsubscribe@jakarta.apache.org
> > For additional commands, e-mail: tomcat-user-help@jakarta.apache.org
> >
> >
>
>
>
>---------------------------------------------------------------------
>To unsubscribe, e-mail: tomcat-user-unsubscribe@jakarta.apache.org
>For additional commands, e-mail: tomcat-user-help@jakarta.apache.org


____________________________________
Justin Ruthenbeck
Software Engineer, NextEngine Inc.
justinr - AT - nextengine DOT com
Confidential
    See http://www.nextengine.com/confidentiality.php
____________________________________


---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-user-help@jakarta.apache.org


RE: How to synchronize based on session? (Prevent multiple submissions of forms)

Posted by Raiden <ra...@wonko.inow.com>.

On Wed, 2 Jul 2003, Stefan Radzom wrote:
> > -----Original Message-----
> > From: Raiden [mailto:raiden@wonko.inow.com]
> > Subject: RE: How to synchronize based on session? (Prevent
> > multiple submissions of forms)
> >
> > On Wed, 2 Jul 2003, Stefan Radzom wrote:
> >
> > > > >IMHO, Justin's proposal will not work since the servlet
> > > > container may choose
> > > > >to pool multiple instances of the same servlet class and
> > > > assign an incoming
> > > > >request to an available instance.
> > > >
> > > > Agreed. If the container chooses to pool multiple instances
> > > > of the same
> > > > servlet (I'm assuming this can be the case with Tomcat?),
> > > > then you're right
> > > > -- it won't work.
> > > >
> > >
> > > Sorry, I was a bit too fast here. The spec allows pooling
> > of instances only
> > > if the servlet implements SingleThreadModel. If the class
> > does not implement
> > > STM only one instance per jvm is allowed.
> >
> > Hrm... I always thought this was opposite of what happens.  I
> > thought we
> > should always assume that there will be multiple instances of of our
> > servlet in existance (for purposes of pooling), and as such,
> > our service
> > methods must take that into account.
> >
>
> Just to clarify some things. If your servlet _does not_ implement STM there
> will be only one instance per jvm. However, there will be no bottleneck
> since multiple threads may execute in the service method at a time.
>
> If your servlet instead _does_ implement STM you are guaranteed that only
> one thread will execute in the service method at a time. To compensate for
> this bottleneck pooling of instance is allowed.
>
> Viewed from this perspective the spec absoultely does make sense.
>
> -Stefan
>

Thank you for the clarification!  I understand now.

-Raiden

---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-user-help@jakarta.apache.org


RE: How to synchronize based on session? (Prevent multiple submissions of forms)

Posted by Stefan Radzom <sr...@web.de>.

> -----Original Message-----
> From: Raiden [mailto:raiden@wonko.inow.com] 
> Sent: Wednesday, July 02, 2003 4:42 AM
> To: Tomcat Users List
> Subject: RE: How to synchronize based on session? (Prevent 
> multiple submissions of forms)
> 
> 
> 
> On Wed, 2 Jul 2003, Stefan Radzom wrote:
> 
> > > >IMHO, Justin's proposal will not work since the servlet
> > > container may choose
> > > >to pool multiple instances of the same servlet class and
> > > assign an incoming
> > > >request to an available instance.
> > >
> > > Agreed. If the container chooses to pool multiple instances
> > > of the same
> > > servlet (I'm assuming this can be the case with Tomcat?),
> > > then you're right
> > > -- it won't work.
> > >
> >
> > Sorry, I was a bit too fast here. The spec allows pooling 
> of instances only
> > if the servlet implements SingleThreadModel. If the class 
> does not implement
> > STM only one instance per jvm is allowed.
> 
> Hrm... I always thought this was opposite of what happens.  I 
> thought we
> should always assume that there will be multiple instances of of our
> servlet in existance (for purposes of pooling), and as such, 
> our service
> methods must take that into account.
> 
> It seems strange that Tomcat would only use a single instance 
> of a servlet
> across all requests if a servlet does not implement STM.  It 
> would appear
> to be a major bottleneck.  In fact, I thought that's the 
> purpose of STM...
> for cases in which you're ok with the bottleneck, but you 
> want to ensure
> that there is only one thread ever accessing that servlet at 
> a given time
> (which would also imply that there is only one instance of 
> the servlet).
> 

Just to clarify some things. If your servlet _does not_ implement STM there
will be only one instance per jvm. However, there will be no bottleneck
since multiple threads may execute in the service method at a time.

If your servlet instead _does_ implement STM you are guaranteed that only
one thread will execute in the service method at a time. To compensate for
this bottleneck pooling of instance is allowed.

Viewed from this perspective the spec absoultely does make sense. 

-Stefan




---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-user-help@jakarta.apache.org


RE: How to synchronize based on session? (Prevent multiple submissions of forms)

Posted by Raiden <ra...@wonko.inow.com>.
On Wed, 2 Jul 2003, Stefan Radzom wrote:

> > >IMHO, Justin's proposal will not work since the servlet
> > container may choose
> > >to pool multiple instances of the same servlet class and
> > assign an incoming
> > >request to an available instance.
> >
> > Agreed. If the container chooses to pool multiple instances
> > of the same
> > servlet (I'm assuming this can be the case with Tomcat?),
> > then you're right
> > -- it won't work.
> >
>
> Sorry, I was a bit too fast here. The spec allows pooling of instances only
> if the servlet implements SingleThreadModel. If the class does not implement
> STM only one instance per jvm is allowed.

Hrm... I always thought this was opposite of what happens.  I thought we
should always assume that there will be multiple instances of of our
servlet in existance (for purposes of pooling), and as such, our service
methods must take that into account.

It seems strange that Tomcat would only use a single instance of a servlet
across all requests if a servlet does not implement STM.  It would appear
to be a major bottleneck.  In fact, I thought that's the purpose of STM...
for cases in which you're ok with the bottleneck, but you want to ensure
that there is only one thread ever accessing that servlet at a given time
(which would also imply that there is only one instance of the servlet).

Thanks,
-Raiden


---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-user-help@jakarta.apache.org


RE: How to synchronize based on session? (Prevent multiple submissions of forms)

Posted by Stefan Radzom <sr...@web.de>.
> -----Original Message-----
> From: Justin Ruthenbeck [mailto:justinr@nextengine.com] 
> Sent: Wednesday, July 02, 2003 2:20 AM
> To: Tomcat Users List
> Subject: RE: How to synchronize based on session? (Prevent 
> multiple submissions of forms)
> 
> 
> At 04:39 PM 7/1/2003, you wrote:
> >IMHO, Justin's proposal will not work since the servlet 
> container may choose
> >to pool multiple instances of the same servlet class and 
> assign an incoming
> >request to an available instance.
> 
> Agreed. If the container chooses to pool multiple instances 
> of the same 
> servlet (I'm assuming this can be the case with Tomcat?), 
> then you're right 
> -- it won't work.
> 

Sorry, I was a bit too fast here. The spec allows pooling of instances only
if the servlet implements SingleThreadModel. If the class does not implement
STM only one instance per jvm is allowed.

Given a non-distributed environment I would still prefer synchronizing on a
session attribute. Code like 

  Object lock = request.getAttribute("lock");
  synchronized (lock) { ; }

will have the desired effect.

> >Things get even worse in a distributed
> >environment involving multiple tomcats running in different jvms.
> >Because these requests are handled by multiple threads and maybe even
> >multiple servlet instances, the one thing in common to all 
> those requests is
> >the session object. IMO, a session attribute is the 
> preferable object to
> >sync.
> 
> (The above was spliced together)
> If you're in a distributed environment, then synching on a session 
> attribute Object won't work either -- different JVMs have 
> different copies 
> of the same object.  The problem is that *any* 
> synchronization gets worse 
> when you're in a distributed environment.  If this is the case, then 
> nothing on the servlet level can *GUARANTEE* a lock -- enter 
> transactions.
> 

> 
> I don't see how this is possible without a third resource (such as a 
> database or other tx manager), but would like to know if you 
> can come up 
> with something.
> 

You are right. In a distributed scenario additional means are needed:

* db or tx manager like you suggested
* use sticky sessions and implement like you would in a non-distributed
environment

-Stefan



---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-user-help@jakarta.apache.org