Dealing with side-effects and concurrency issues

30 zobrazení
Preskočiť na prvú neprečítanú správu

Alen Ribic

neprečítané,
17. 12. 2009, 9:59:5717. 12. 2009
komu: Compojure
I am trying to run a simple load test, using apache benchmark, on
simple code example below.
The code below runs fine until you increase the concurrency level.
When concurrency level goes up, it starts throwing exceptions in my
function "test-create-label" that contains side-effects.

============
web.clj
============

(ns com.myapp.service.web
(:gen-class)
(:use [compojure]
[compojure.http response])
(:import [org.springframework.web.context ContextLoaderListener]
[org.springframework.web.context.support
WebApplicationContextUtils]
[com.myapp.core.model Label]))

(defn test-create-label [request]
(do
(let [spring-ctx
(WebApplicationContextUtils/getWebApplicationContext
(request :servlet-context))
label-dao (.getBean spring-ctx "labelDao")
new-label (doto (new Label)
(.setName "Label 1")
(.setDescription "testing"))]
(.create label-dao new-label)
(.read label-dao (.getId new-label)))))

(defn test-page [request]
(html [:html
[:head]
[:body
[:p "Test page."]
[:p "Label name: " (test-create-label request)]]]))

;; define routes
(defroutes webservice
(GET "/"
(test-page request))
(ANY "*"
(page-not-found)))

(defn start-server [host port]
(defserver ws-server {:host host :port port}
"/*" (servlet webservice))
(let [ctx (get-context ws-server)]
(doto ctx
(.setInitParams {"contextConfigLocation"
"classpath:/myapp-core-context.xml"})
(.addEventListener (new ContextLoaderListener)))
(start ws-server)))

(defn -main [& args]
(start-server "127.0.0.1" 8080))


The test-create-label function binds the spring framework context to
local var spring-ctx and it then binds the label-dao to a lookup of a
persistence DAO object in the spring-ctx and then the new label-new
gets bound to the new Label entity instance. Finally it creates the
new label (with relevant properties) and returns the label by reading
it from the label-dao bean.

Above works without a hitch until you do a load test with some more
concurrent calls.
Exception I get is from the hibernate framework (called in the test-
create-label during the .create process) that is configured in the
spring context:

============
org.hibernate.NonUniqueObjectException: a different object with the
same identifier value was already associated with the session:
[com.myapp.core.model.Label#13715]
============

It seems to me that multiple concurrent threads (requests) enter the
test-create-label function which results in the above exception. Could
this be the cause? If so, what is the correct way in Compojure to deal
with side-effects as in test-create-label function?

Regards,
-Alen Ribic

Shantanu Kumar

neprečítané,
17. 12. 2009, 11:14:5917. 12. 2009
komu: comp...@googlegroups.com
This seems to be related to the way you are handling Hibernate sessions. As per the Hibernate doc:


This exception is thrown when an operation would break session-scoped identity. This occurs if the user tries to associate two different instances of the same Java class with a particular identifier, in the scope of a single Session.

This scenario might happen if you are using the same Hibernate session object to create different objects (concurrently) with same identifier.

Regards,
Shantanu 

Alen Ribic

neprečítané,
17. 12. 2009, 11:37:1517. 12. 2009
komu: Compojure
Actually, I found a solution, hopefully optimal one.

Firstly, like HttpServlet service() method works, multiple threads can
call the service method on single servlet instance. Thats a give.
This would mean that my test-create-label function would indeed
receive multiple simulations requests.
All good so far, until we hit the java lower-level calls that is.
So at this point I ask what concurrency solution does apply in this
scenarion. The only one I can tell is clojure "locking".

So I tried an explicit lock just around sprint-ctx calls using the
spring-ctx and locking macro as follows, and it worked:

(defn test-create-label [request]


(let [spring-ctx
(WebApplicationContextUtils/getWebApplicationContext
(request :servlet-context))
label-dao (.getBean spring-ctx "labelDao")
new-label (doto (new Label)
(.setName "Label 1")
(.setDescription "testing"))]

(locking spring-ctx


(.create label-dao new-label)
(.read label-dao (.getId new-label)))))


No to sure this is the best way to go about this. I'd appreciate any
further input.

-Alen


On Dec 17, 6:14 pm, Shantanu Kumar <kumar.shant...@gmail.com> wrote:

> URL:https://www.hibernate.org/hib_docs/v3/api/org/hibernate/NonUniqueObje...

Shantanu Kumar

neprečítané,
17. 12. 2009, 11:45:5817. 12. 2009
komu: comp...@googlegroups.com
On Thu, Dec 17, 2009 at 10:07 PM, Alen Ribic <alen....@gmail.com> wrote:
Actually, I found a solution, hopefully optimal one.

Firstly, like HttpServlet service() method works, multiple threads can
call the service method on single servlet instance. Thats a give.
This would mean that my test-create-label function would indeed
receive multiple simulations requests.
All good so far, until we hit the java lower-level calls that is.
So at this point I ask what concurrency solution does apply in this
scenarion. The only one I can tell is clojure "locking".

So I tried an explicit lock just around sprint-ctx calls using the
spring-ctx and locking macro as follows, and it worked:

(defn test-create-label [request]
 (let [spring-ctx
        (WebApplicationContextUtils/getWebApplicationContext
(request :servlet-context))
       label-dao (.getBean spring-ctx "labelDao")
       new-label (doto (new Label)
                   (.setName "Label 1")
                   (.setDescription "testing"))]
   (locking spring-ctx
     (.create label-dao new-label)
     (.read label-dao (.getId new-label)))))


No to sure this is the best way to go about this. I'd appreciate any
further input.


If label-dao is scoped as a singleton, locking label-dao would be a better idea. Better yet, you can maintain a set of identifiers-in-flight (being persisted) and wait (with a timeout) until it doesn't contain the ID you are trying to access.

Regards,
Shantanu

Alen Ribic

neprečítané,
17. 12. 2009, 11:56:3617. 12. 2009
komu: Compojure
Thank Shantanu. You are right. My dao instances are singles as per
spring-context default.
I also moved the setting of the setters into the locking block as it
wouldn't really work outside.

(defn test-create-label [request]
(let [spring-ctx
(WebApplicationContextUtils/getWebApplicationContext
(request :servlet-context))
label-dao (.getBean spring-ctx "labelDao")

new-label (Label.)]
(locking label-dao
(doto new-label


(.setName "Label 1")
(.setDescription "testing"))

(.create label-dao new-label)
(.read label-dao (.getId new-label)))))


If this is the best way, I'll probably go ahead and abstract out this
complexity into a macro.

Regards,
-Alen


On Dec 17, 6:45 pm, Shantanu Kumar <kumar.shant...@gmail.com> wrote:

Shantanu Kumar

neprečítané,
17. 12. 2009, 12:12:0717. 12. 2009
komu: comp...@googlegroups.com
On Thu, Dec 17, 2009 at 10:26 PM, Alen Ribic <alen....@gmail.com> wrote:
Thank Shantanu. You are right. My dao instances are singles as per
spring-context default.
I also moved the setting of the setters into the locking block as it
wouldn't really work outside.

(defn test-create-label [request]
 (let [spring-ctx
        (WebApplicationContextUtils/getWebApplicationContext
(request :servlet-context))
       label-dao (.getBean spring-ctx "labelDao")
       new-label (Label.)]
   (locking label-dao
     (doto new-label
                   (.setName "Label 1")
                   (.setDescription "testing"))
     (.create label-dao new-label)
     (.read label-dao (.getId new-label)))))


If this is the best way, I'll probably go ahead and abstract out this
complexity into a macro.


This does not seem to be an optimum solution to me because it will reduce the DAO to one request at a time (means zero concurrency). A better option would be to obtain a new Hibernate Session on every request and do your persistence there -- this option needs no locking. Maybe if you can share your DAO code that will reveal how you are obtaining the Hibernate Session and will provide a clue to what a correct fix might be.

Regards,
Shantanu

Alen Ribic

neprečítané,
17. 12. 2009, 13:22:4217. 12. 2009
komu: Compojure
That sound right. Associating the hibernate session per request would
be the way to go then there would be no explicit locking required.
Here is the sprint-context app xml file:
You will see the hibernate config there too.
Thanks Shantanu.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

<!-- Data Source Setup -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource" destroy-
method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/
>
<property name="url" value="jdbc:hsqldb:file:dev_db/myapp-
testdb"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>

<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>mappings.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.hbm2ddl.auto=update
</value>
</property>
</bean>

<bean id="namingStrategy"

class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField">
<value>org.hibernate.cfg.ImprovedNamingStrategy.INSTANCE</
value>
</property>
</bean>

<bean id="extendedFinderNamingStrategy"

class="com.myapp.core.persist.dao.finder.impl.ExtendedFinderNamingStrategy"/
>


<!-- Dao Layer generic config-->
<bean id="finderIntroductionAdvisor"
class="com.myapp.core.persist.dao.finder.impl.FinderIntroductionAdvisor"/
>
<bean id="abstractDaoTarget"

class="com.myapp.core.persist.dao.impl.GenericDaoHibernateImpl"
abstract="true">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
<property name="namingStrategy">
<ref bean="extendedFinderNamingStrategy" />
</property>
</bean>
<bean id="abstractDao"
class="org.springframework.aop.framework.ProxyFactoryBean"
abstract="true">
<property name="interceptorNames">
<list>
<value>finderIntroductionAdvisor</value>
<value>transactionInterceptor</value>
</list>
</property>
</bean>

<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>

<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="transactionAttributeSource">
<value>

com.myapp.core.persist.dao.impl.GenericDaoHibernateImpl.create=PROPAGATION_REQUIRED

com.myapp.core.persist.dao.impl.GenericDaoHibernateImpl.update=PROPAGATION_REQUIRED

com.myapp.core.persist.dao.impl.GenericDaoHibernateImpl.delete=PROPAGATION_REQUIRED
</value>
</property>
</bean>


<!-- Dao Layer instances -->
<bean id="labelDao" parent="abstractDao">
<property name="proxyInterfaces">
<value>com.myapp.core.persist.dao.LabelDao</value>
</property>
<property name="target">
<bean parent="abstractDaoTarget">
<constructor-arg>
<value>com.myapp.core.model.Label</value>
</constructor-arg>
</bean>
</property>
</bean>

<bean id="emailAddressDao" parent="abstractDao">
<property name="proxyInterfaces">
<value>com.myapp.core.persist.dao.EmailAddressDao</value>
</property>
<property name="target">
<bean parent="abstractDaoTarget">
<constructor-arg>
<value>com.myapp.core.model.EmailAddress</value>
</constructor-arg>
</bean>
</property>
</bean>

<bean id="senderDao" parent="abstractDao">
<property name="proxyInterfaces">
<value>com.myapp.core.persist.dao.SenderDao</value>
</property>
<property name="target">
<bean parent="abstractDaoTarget">
<constructor-arg>
<value>com.myapp.core.model.Sender</value>
</constructor-arg>
</bean>
</property>
</bean>

<bean id="billDao" parent="abstractDao">
<property name="proxyInterfaces">
<value>com.myapp.core.persist.dao.BillDao</value>
</property>
<property name="target">
<bean parent="abstractDaoTarget">
<constructor-arg>
<value>com.myapp.core.model.Bill</value>
</constructor-arg>
</bean>
</property>
</bean>

<bean id="powerBillDao" parent="abstractDao">
<property name="proxyInterfaces">
<value>com.myapp.core.persist.dao.PowerBillDao</value>
</property>
<property name="target">
<bean parent="abstractDaoTarget">
<constructor-arg>
<value>com.myapp.core.model.PowerBill</value>
</constructor-arg>
</bean>
</property>
</bean>


<!-- Business Layer instances -->
<!-- ... -->
</beans>


Regards,
-Alen

Alen Ribic

neprečítané,
17. 12. 2009, 13:30:2517. 12. 2009
komu: Compojure
Dam it, I forgot OpenSessionInViewFilter. It binds hibernate session
per incoming request thread. :)

-Alen


On Dec 17, 8:22 pm, Alen Ribic <alen.ri...@gmail.com> wrote:
> That sound right. Associating the hibernate session per request would
> be the way to go then there would be no explicit locking required.
> Here is the sprint-context app xml file:
> You will see the hibernate config there too.
> Thanks Shantanu.
>
> <?xml version="1.0" encoding="UTF-8"?>
> <beans xmlns="http://www.springframework.org/schema/beans"
>        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>        xmlns:context="http://www.springframework.org/schema/context"
>        xmlns:jee="http://www.springframework.org/schema/jee"
>        xmlns:util="http://www.springframework.org/schema/util"
>        xmlns:tx="http://www.springframework.org/schema/tx"
>        xmlns:aop="http://www.springframework.org/schema/aop"
>        xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd
>        http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd
>        http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-2.0.xsd
>        http://www.springframework.org/schema/jeehttp://www.springframework.org/schema/jee/spring-jee-2.5.xsd
>        http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.0.xsd

>        http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

Odpovedať všetkým
Odpovedať autorovi
Poslať ďalej
0 nových správ