Sep 252007
 

Regulär geht man davon aus, dass die servergespeicherten Daten einer User Session immer nur von einem Thread zur selben Zeit gelesen bzw. geschrieben werden. Es ist zu aufwendig, jede Stelle im Code welche mit der User Session arbeitet vor Race Conditions durch Mutext-Objekte / Monitore zu schützen. Daher muss dafür gesorgt werden, dann ein Request aus einer User Session immer nacheinander aber nicht parallel verarbeitet werden. Im Folgenden wird ein Code-Beispiel für J2EE Web Applikationen gegeben.

Um Race Conditions bezogen auf die User Session zu vermeiden, muss man dafür sorgen, dass immer nur ein HTTP Request einer User Session zur selben Zeit verarbeitet wird. Dazu muss das Mutex-Handle in der User Session selbst hinterlegt werden. Ein Request darf nur verarbeitet werden, wenn es die Sperre auf den Mutex hält. Diese Sperre wird für die Dauer der Request-Verarbeitung vom Thread gehalten und danach freigegeben. Kann ein Request die Sperre nicht exklusiv beanspruchen, weil der Mutex von einem anderen Thread gesperrt wird, so muss die Verarbeitung des HTTP Requests warten.

Für J2EE Web Applikationen lässt sich dies durch ein in der Session gespeichertes Objekt, einen Servletfilter und einen synchronized-Block realisieren. Der Code sieht dann wie folgt aus:

package com.openwishes.presentation.servletfilter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * In order to prevent race conditions in the request processing logic when interacting with session data, etc.,
 * requests of the same user session should be serialized.
 * This means that only one requests of the same user session is processed at the same time.
 * This is achieved through a lock object which is kept in the user session.
 *
 * @author MN
 */
public class SerializeRequestsServletFilter implements Filter {

  //~ inner classes ////////////////////////////////////////////////////////////////////////////////////////////////////

  //~ class fields /////////////////////////////////////////////////////////////////////////////////////////////////////

  private static final String KEY_SERIALIZE_REQUESTS_LOCK_OBJ = "SerReqLock";

  private static final Log log = LogFactory.getLog(SerializeRequestsServletFilter.class);

  //~ instance fields //////////////////////////////////////////////////////////////////////////////////////////////////

  //~ constructors /////////////////////////////////////////////////////////////////////////////////////////////////////

  public SerializeRequestsServletFilter() {
    log.info("created servlet filter for serializing requests in the same user session");
  }

  //~ methods //////////////////////////////////////////////////////////////////////////////////////////////////////////

  /**
   * {@inheritDoc}
   */
  public void destroy() {
    // nothing to do
  }

  /**
   * {@inheritDoc}
   */
  public void doFilter(ServletRequest pReq, ServletResponse pRes, FilterChain pChain) throws IOException,
    ServletException {

    HttpSession session = ((HttpServletRequest) pReq).getSession();

    Object lock;
    synchronized (session) {
      lock = session.getAttribute(KEY_SERIALIZE_REQUESTS_LOCK_OBJ);
      if (lock == null) {
        lock = new Object();
        session.setAttribute(KEY_SERIALIZE_REQUESTS_LOCK_OBJ, lock);
      }
    }

    synchronized (lock) {
      pChain.doFilter(pReq, pRes);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void init(FilterConfig pConfig) throws ServletException {
    // nothing to do
  }

  //~ static methods ///////////////////////////////////////////////////////////////////////////////////////////////////

}

Wichtig ist, dass der Code zum Ablegen / Ermitteln des Lock-Objekts selbst vor Race Conditions geschützt ist und dass in jeder Session ein seperates Lock-Objekt verwendet wird.

Durch diesen einfachen Mechanismus sind die Daten in der User Session hinreichend gut vor Race Conditions aus parallel verarbeiteten HTTP-Requests geschützt.

  4 Responses to “Schutz für Web User Session vor Race Conditions – Serialisierung von HTTP Requests”

  1. Interessanter Ansatz, allerdings wird damit die parallele Verarbeitung zweier Requests zu einer Session doch generell verhindert, oder? Selbst wenn das Servlet, welches den Request bearbeitet, nicht auf die Session zugreift. Ich würde dann eher einen Wrapper um die Session schreiben, welche Zugriffe auf einzelne Attribute schützt Wenn es um den Zugriff auf mehrere Session-Atrribute geht, die einen gemeinsamen konsistenten Zustand aufweisen müssen, dafür sollte der Wrapper das Lock selber zur Verfügung stellen.

  2. Richtig, die parallele Verarbeitung von Requests zu einer Sessions wird generell durch diesen Ansatz verhindert. Wenn das Servlet, welches den Request bearbeitet, nicht auf die Session zugreift, dann braucht man den beschriebenen Servlet-Filter nicht davorzuschalten.

    Die Idee mit dem Session-Wrapper ist auch eine Richtung. Nehmen wir an in der Session liegt eine Collection. Du hast den Wrapper welcher um den Zugriff auf die Session in der Sitzung (set / get) liegt und konkurrierende Zugriffe ausschließt. Wie vermeidet Dein Ansatz dann die Atomizität von Operationen auf der Collection, wie zum Beispiel: Ein Element wird in der Collection gesucht. Wenn dieses nicht vorhanden ist, wird ein Element eingefügt. Dann braucht man eine Sperre für diese gesamte Operation und nicht nur für das set / get der Collection in der Session, oder?

    Es hängt natürlich von den Anforderungen ab, jedoch ist zumeist das parallele Bearbeiten von Requests einer Session (User) nicht notwendig. Statischer Content, den der Web Browser auch parallel anfordert, wird in den meisten Infrastrukturen durch einen WebServer wie Apache ausgeliefert. Mir taugt der Servlet-Filter ganz gut.

  3. Netter Ansatz aber:

    – Es handelt sich um einen ServletFilter. Mit einem entsprechenden Filter Mapping in der web.xml könnt Ihr entscheiden welche Bereiche der Anwendung nicht parallel betreten werden darf.
    – Wenn der Entwickler entscheiden kann, das die Sessiondaten zwei Anfragen im geschützten Bereich wirklich disjunkt sind, kann man die Öffnung
    mit einem zusätzlichen Request Parameter zu lassen.
    – Der Ansatz verhindert allerdings keine RaceConditions mit den Session Manager des Webcontainer. Bei einem Clusterbetrieb im Tomcat müsste diese Implementierung also ehr ein Valve vor dem ReplicationValve sein :-)
    – Warum reicht Dir der Mutex auf der Session nicht aus?

  4. Hallo Peter,

    natürlich liegst Du richtig: da der obige Ansatz mittels ServletFilter umgesetzt ist, kann mittels im web.xml Deskriptor steuern, für welche URIs die Serialisierung von Requests angewendet werden soll. Einen Request-Parameter für die Steuerung dieses Verhaltens würde ich persönlich nicht bevorzugen, da hierdurch der Client Einfluß auf diese Steuerung nehmen kann.

    Bzgl. verteilter Sessions: Bei Session-Replikation im Cluster reicht dieser Ansatz nicht, da nur alle Requests innerhalb einer WebApp-Instanz serialisiert werden. Meine Erfahrung ist jedoch, dass auf Session-Replikation verzichtet und mit sogenannten “sticky web sessions” gearbeitet wird. Der Load-Balancer leitet alle Requests einer Session an denselben Host des Clusters weiter, so dass auf Session-Replikation verzichtet werden kann. Beim Herunterfahren / Ausfall einer WebApp-Instanz auf einem Host eines Clusters gehen zwar alle Sitzungen verloren, jedoch ist dies bei vielen (Unternehmens-) Anwendungen durchaus akzeptabel.

    Der obige Ansatz kann mittels Deines Vorschlags: Mutex / Monitor (synchronized-Block) auf das Session-Objekt selbst, statt eines Objekts in der Session vereinfachen. Ich denke, das ist noch um ein weiteres eleganter. Danke dafür.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>