tomcat-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Justin Ruthenbeck <just...@nextengine.com>
Subject RE: How to synchronize based on session? (Prevent multiple submissions of forms)
Date Wed, 02 Jul 2003 00:19:45 GMT
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


Mime
View raw message