-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 
Author Message
 Post subject: no HibernateException on transaction deadlock
PostPosted: Thu Mar 02, 2006 2:38 pm 
Newbie

Joined: Thu Mar 02, 2006 1:43 pm
Posts: 6
Software: Hibernate 3.1.2, MySQL 5 (MySQLMyISAMDialect), MySQL JDBC 5

I'm trying to get familiar with transaction handling using Hibernate. In particular I'm trying to run the "GenerateDeadlock" example in chapter 9 of "Pro Hibernate3" published by Apress (www.apress.com has the source code). That example consists of 2 threads each of which is trying to update the same 2 tables, but in opposite order so as to create deadlock because the 2 updates each thread performs are part of the same transaction. Pretty basic stuff. Each thread task is coded to catch a HibernateException upon deadlock at which time an explicit rollback is performed. This should result in both tables containing the same value as only one transaction will succeed. However, no HibernateException is ever caught! In fact no exception of any kind is being thrown by the run method of the Task body (I added a catch for Throwable). I am completely baffled by this. The authors of the code apparently thought their code was correct, and having spent a lot of time with the documentation I have found nothing that might suggest they're wrong. It gets worse. Not only is no exception being thrown, but the transaction as delineated by Hibernate is not being honored. Most of the time the 2 units of work in each thread transaction (called step1 & step2 in the code) are being treated as individual transactions which is totally contrary to expectation. Sometimes the two tables contain the same value suggesting that only one transaction succeeded, but as I mentioned previously this is happening without a HibernateException and explicit rollback. The fact that the results are not the same on all runs of GenerateDeadlock makes this even more serious, it basically means these software components are unusable in combination. It may be that the JDBC driver or MySQL is munging something up, but shouldn't Hibernate be bubbling up some kind of status indication whether it be a Hibernate exception or a rethrow of an exception caught by Hibernate? Any suggestions/ideas on how to get a handle on this are most welcome.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 02, 2006 2:46 pm 
Regular
Regular

Joined: Wed Feb 08, 2006 3:59 pm
Posts: 75
I see your using MySQLMyISAMDialect, does it mean that your usin MyISAM storage engine ? It's not transactionnal AFAIK...

If not could you post the code your trying to run, it would help.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 02, 2006 3:04 pm 
Expert
Expert

Joined: Fri Aug 19, 2005 2:11 pm
Posts: 628
Location: Cincinnati
the-gtm wrote:
If not could you post the code your trying to run, it would help.


and using the [return] key more often :)

_________________
Chris

If you were at work doing this voluntarily, imagine what you'd want to see to answer a question.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 02, 2006 3:28 pm 
Newbie

Joined: Thu Mar 02, 2006 1:43 pm
Posts: 6
[quote="the-gtm"]I see your using MySQLMyISAMDialect, does it mean that your usin MyISAM storage engine ? It's not transactionnal AFAIK...

If not could you post the code your trying to run, it would help.[/quote]

I didn't install the InnoDB MySQL binary so I thought MyISAMDialect was appropriate. You are correct about transactions. The MySQL Reference Manual indicates only InnoDB and BDB as TST (transaction-safe tables) supporting COMMIT batching and ROLLBACK. My bad. Nevertheless, that doesn't explain Hibernate's lack of grace in passing back status to the application. Here's the code:
BTW, this code has a minor bug, it shouldn't be looking for username 'test' after the 1st table update. Just assume the initial 'test' creation succeeds and use the query creation of form:
Query query = session.createQuery("select p from Publisher as p");
and comment out the subsequent query.setString().

package com.hibernatebook.chapter09.deadlock;

import java.sql.Connection;
import java.sql.SQLException;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class GenerateDeadlock {

public abstract static class Task implements Runnable {

// Specify the name of the task to carry out
// and the name of the user to look up and
// modify
public Task(String taskName, String username) {
this.username = username;
this.taskName = taskName;
}

abstract public void step1(Session session);

abstract public void step2(Session session);

// Carry out two database steps with a pause between
// them.
public void run() {
Session session = sessions.openSession();
Transaction tx = null;
try {
System.out.println(taskName + " begins a transaction.");
tx = session.beginTransaction();

// Step 1
System.out.println(taskName + " step 1");
step1(session);

// Pause to ensure proper ordering
// of the steps. You would never
// normally do this!
pause();

// Step 2
System.out.println(taskName + " step 2");
step2(session);

tx.commit();
System.out.println(taskName + " committed its transaction.");
tx = null;
} catch (HibernateException e) {
if (tx != null)
tx.rollback();
System.out.println(taskName+ " rolled back its transaction: " + e);
} finally {
System.out.println("Session for " + taskName + " closed.");
session.close();
}
}

private void pause() {
setPaused(true);
while (isPaused()) {
synchronized (this) {
try {
System.out.println("Pausing " + taskName);
wait();
} catch (InterruptedException e) {
// No need to process this.
}
}
}
System.out.println(taskName + " awoken, continuing...");
}

public boolean isPaused() {
return isPaused;
}

public void setPaused(boolean isPaused) {
this.isPaused = isPaused;
if (!isPaused) {
synchronized (this) {
notifyAll();
}
}
}

protected String taskName;
protected String username;
private boolean isPaused = false;
}

private static SessionFactory sessions = new Configuration().configure().buildSessionFactory();

/*
// Create a user in the database - the alternative
// implementation making standard usage of the
// session's own transaction methods but using
// the default (global) isolation level.
public static void createUser(String username)
throws HibernateException {
Session session = sessions.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();

// Normal usage of the Session here...
Publisher p = new Publisher(username);
Subscriber s = new Subscriber(username);
session.saveOrUpdate(p);
session.saveOrUpdate(s);

tx.commit();
tx = null;
} catch (HibernateException e) {
// Handle a failed transaction
if (tx != null)
tx.rollback();
} finally {
// Close the session
session.close();
}
}
*/

// Create a user in the database - illustrates
// explicit setting of the isolation transaction
// for a single transaction.
public static void createUser(String username)
throws HibernateException {
Session session = sessions.openSession();
Connection conn = null;

int isolation = -1;
try {
// Obtain the JDBC connection
conn = session.connection();

// Determine the initial isolation level of the transaction
isolation = conn.getTransactionIsolation();

// Explicitly set the transaction's isolation level
// before beginning the transaction.
conn
.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
conn.setAutoCommit(false);

// Normal usage of the Session here...
Publisher p = new Publisher(username);
Subscriber s = new Subscriber(username);
session.saveOrUpdate(p);
session.saveOrUpdate(s);

// Force data to be written to the database
// BEFORE the transaction is committed.
session.flush();
// Commit the transaction
conn.commit();
} catch (Exception e1) {
// Handle a failed transaction
try {
if (conn != null) {
conn.rollback();
}
} catch (SQLException e2) {
System.out.println("Could not rollback the connection: "
+ e2);
}
} finally {
// Restore the original isolation of the committed
// or rolled back transaction
if (conn != null) {
if (isolation != -1) {
try {
conn.setTransactionIsolation(isolation);
} catch (SQLException e2) {
System.out
.println("Could not restore the connection isolation level: "
+ e2);
}
}
}

// Close the session
session.close();
}
}

public static void main(String[] argv) {
String username = "test";

// Firstly we'll create the Publisher and
// Subscriber.
createUser(username);

// We create a task (ready to be run) which will
// update the Publisher, pause, then update the
// Subscriber.
Task taskA = new Task("Task A", username) {
public void step1(Session session) {
Query query = session
.createQuery("from Publisher where :foo = username");
query.setString("foo", username);
Publisher p = (Publisher) query.uniqueResult();
p.setUsername("jeff");
}

public void step2(Session session) {
Query query = session
.createQuery("from Subscriber where :foo = username");
query.setString("foo", username);
Subscriber s = (Subscriber) query.uniqueResult();
s.setUsername("jeff");
}
};

// We create a task (ready to be run) which will
// update the Subscriber, pause, then update the
// Publisher (the opposite order to TaskA).
Task taskB = new Task("Task B", username) {
public void step1(Session session) {
Query query = session
.createQuery("from Subscriber where username = :who");
query.setString("who", username);
Subscriber s = (Subscriber) query.uniqueResult();
s.setUsername("dave");
}

public void step2(Session session) {
Query query = session
.createQuery("from Publisher where username = :who");
query.setString("who", username);
Publisher p = (Publisher) query.uniqueResult();
p.setUsername("dave");
}
};

// Run the first step of task A to completion
new Thread(taskA).start();
while (!taskA.isPaused())
;
System.out.println("Task A is complete (paused)");

// Run the first step of task B to completion
new Thread(taskB).start();
while (!taskB.isPaused())
;
System.out.println("Task B is complete (paused)");

// Both tasks have completed their first steps - we'll now
// release the brakes from both of them, and see what
// happens.

taskA.setPaused(false);
taskB.setPaused(false);
}
}


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 02, 2006 5:43 pm 
Newbie

Joined: Thu Mar 02, 2006 1:43 pm
Posts: 6
Good news, bad news.

The good news is the conversion to InnoDB is quite trivial and as a result transactions as delineated by Hibernate are now observed. So what seems to be happening now with InnoDB is that MySQL detects the deadlock and performs rollback on the clients behalf.

That sounds good on the surface, but how is the application to know that the DB performed a rollback?? There are still no exceptions being thrown. Will the Transaction method wasRolledBack() address this issue? I'll give it a try, but I'm not holding my breath. :-)


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 02, 2006 6:03 pm 
Newbie

Joined: Thu Mar 02, 2006 1:43 pm
Posts: 6
Nope, wasRolledBack() is never true. Worse yet, wasCommitted() is actually true for the transaction that was rolled back!! What a mess. So as far as I can tell there is no way for an application to determine if rollback was performed by the DB. I think this rises to the level of seriousness that one of the Hibernate gurus ought to take a look. Thank you.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Mar 04, 2006 4:32 pm 
Newbie

Joined: Thu Mar 02, 2006 1:43 pm
Posts: 6
Ok, there were no deadlocks taking place because I incorrectly assumed pessimistic locking with the isolation level commented out in the properties file. I should've RTFM. Argh.

That has been corrected and indeed deadlocks are now generated. But they're not being detected. Instead a "Lock wait timeout" is generated. And I can't find anything in the documentation that shows how to induce the desired NOWAIT behavior. I tried query.setLockMode(), but it had no effect. Stay tuned.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Mar 05, 2006 8:04 pm 
Newbie

Joined: Thu Mar 02, 2006 1:43 pm
Posts: 6
[quote="nbi"]Ok, there were no deadlocks taking place because I incorrectly assumed pessimistic locking with the isolation level commented out in the properties file. I should've RTFM. Argh.

That has been corrected and indeed deadlocks are now generated. But they're not being detected. Instead a "Lock wait timeout" is generated. And I can't find anything in the documentation that shows how to induce the desired NOWAIT behavior. I tried query.setLockMode(), but it had no effect. Stay tuned.[/quote]

The Hibernate developer who looked at this issue claims to have generated deadlock and the subsequent HibernateException. According to him the missing ingredients were to adjust the isolation level and to issue explicit flush() calls after step1() and step2(). I have done exactly that, but am unable to replicate his findings. My theory is that something is amiss with either my hibernate.properties file and/or my MySQL 5 configuration. The Hibernate developer has been extremely hostile and is refusing to send his .properties and MySQL 5 configuration because I'm wasting his time "on stupid cases like this". Having reviewed a large number of bug reports this sort of behavior has been commented upon by many other users and seems to be par for the course now that Hibernate is under the aegis of JBoss. The moral of the story is don't expect any help if they can find an excuse not to give it!

I would very much like to hear from other users who have successfully executed the sample code in question. Any developer evaluating Hibernate as a possible implementation component should be able to obtain the confidence that Hibernate's handling of transactions is consistent and problem free.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Mar 05, 2006 10:08 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
The actual history of this case is that (in private email) you have been unacceptably rude, pigheaded and demanding of people who have absolutely no responsibility to help you in any way. Oh, and your "problem" is totally bogus.

If you write any further insults or rants of this nature you will be immediately banned.

Open source != slavery. Just because someone gives you free software does not mean that you have the right to treat them like shit. This has always been my rule here (long before any involvement of JBoss in the Hibernate project). If you are unable to accept this rule, please do not use my product, and please take your disgusting attitude problem somewhere else.

Thanks in advance.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Mar 05, 2006 11:00 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
For those of you interested in following up with this, here is a link to the JIRA case nbi so graciously created: http://opensource2.atlassian.com/projec ... e/HHH-1541

The case has an attachment containing all the books source code.

After thoroughly looking through the GenerateDeadlock code to which he refers and running the indicated example, I closed the case with my findings and indicated the problem with the underlying assumptions of the test case. Furthermore, I also blogged in depth about it which you can see here: http://blog.hibernate.org/cgi-bin/blosx ... locks.html


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.