Why I don't like FireBird: Part II

Take a look at this code. It is supposed to give me the earliest message, ensuring that I'll get each message once and only once.

It doesn't work. I am getting some messages twice. The same code (well, simplified) just works on SQLite.

public QueueMessage GetEarliestMessage()
{
	byte[] data = null;
	bool done = false;
	while (done == false)
	{
		Transaction(cmd =>
		{
			/* 
				SELECT FIRST 1 Id, Data FROM IncomingMessages
				ORDER BY InsertedAt ASC
			*/
			cmd.CommandText = Queries.GetEarliestMessageFromIncomingQueue;
			string id;
			using (var reader = cmd.ExecuteReader())
			{
				if (reader.Read() == false)
				{
					done = true;
					return;
				}
				id = reader.GetString(0);
				data = (byte[])reader[1];
			}
			/* DELETE FROM IncomingMessages WHERE Id = @Id	*/
			cmd.CommandText = Queries.DeleteMessageFromIncomingQueue;
			cmd.Parameters.Add("@Id", id);
			try
			{
				var rowAffected = cmd.ExecuteNonQuery();
				// someone else already grabbed and deleted this row, 
				// so we will try again with another one
				if (rowAffected != 1)
					return; // same as continue in this case} 
			}
			catch (FbException e)
			{
				// yuck! it would have been better to compare the error code
				// but FB doesn't exposes it
				if (e.Message == "cannot update erased record")
				{
					return;// same as continue
				}
			}
			done = true;// same as break from the loop
		});
	}
	if (data == null)
		return null;
	return Deserialize(data);
}

protected void Transaction(Action<FbCommand> action)
{
	using (var connection = new FbConnection(connectionString))
	using (var cmd = connection.CreateCommand())
	{
		connection.Open();
		using (var tx = connection.BeginTransaction(IsolationLevel.Serializable))
		{
			cmd.Transaction = tx;
			action(cmd);
			tx.Commit();
		}
	}
}

a

Print | posted on Monday, July 07, 2008 12:37 AM

Feedback


Gravatar

# re: Why I don't like FireBird: Part II 7/7/2008 9:19 AM Jelle Hissink

Try
e.Number or e.Errors[0].Number
See:
http://www.firebirdsql.org/dotnetfirebird/documentation/api/1.7/FirebirdSql.Data.Firebird.FbException.html


Gravatar

# re: Why I don't like FireBird: Part II 7/7/2008 10:25 AM Ayende Rahien

That is not the issue, the issue is that there is no transaction isolation maintained


Gravatar

# re: Why I don't like FireBird: Part II 7/7/2008 10:48 AM Frans Bouma

Duh, firebird doesn't have that isolation mode (only some databases do).

What I DO find disturbing in your code are:
1) a big chunk of code in a lambda. IMHO it's better to write a separate routine for that but alas
2) a fetch inside a transaction. Fetches inside transactions are a clue that something is out of order: fetch first, then start transaction.


Gravatar

# re: Why I don't like FireBird: Part II 7/7/2008 10:51 AM Ayende Rahien

Frans,
The code above should work with even with read committed.
Notice that I am explicitly checking that the row was removed, and only then moving on.
I still managed to get duplicate results here.


1) I often use this approach to handle additional concerns. Consider this as a poor man's AOP.
2) I disagree with that. All access to the DB should be done under a transaction.


Gravatar

# re: Why I don't like FireBird: Part II 7/7/2008 2:50 PM Frans Bouma

You mean, the second select shouldn't succeed if someone else already read the row which should have placed a read lock on the row? I'm not sure if Firebird sets read-locks in such a way that multiple reads of the same row isnt possible if the row was already locked for reads.

I'm also not sure if firebird supports hints for locks. Their manual is pretty bad, to say the least.


Gravatar

# re: Why I don't like FireBird: Part II 7/7/2008 2:57 PM Ayende Rahien

There isn't a second select, it is a delete.

Basically, I am doing:

1) give me the first row
2) delete the previously selected row
3) if successfully deleted, return row
4) if failed to delete, go to 1)

That is safe in read committed mode, but it fails with FireBird.


Gravatar

# re: Why I don't like FireBird: Part II 7/8/2008 3:07 PM Filip Kinsky

Serializable and ReadCommited are both supported in FireBird. ReadCommited is default isolation.
http://www.firebirdsql.org/dotnetfirebird/blog/2005/02/transaction-isolation-levels-in.html

could you provide some really simple set of SQL/DDL commands to simulate the error behavior you describe?


Gravatar

# re: Why I don't like FireBird: Part II 7/8/2008 6:21 PM Ayende Rahien

You have the SQL there, and the table structure is 1:1 with that.
Make sure to run this on 3 threads, that is how I got it.


Gravatar

# re: Why I don't like FireBird: Part II 7/8/2008 10:56 PM The Other Steve

This comment:
// someone else already grabbed and deleted this row,
// so we will try again with another one

is shouting semaphore.

Even then, I'm hearing MSMQ whispering in the wind.


Gravatar

# re: Why I don't like FireBird: Part II 7/9/2008 6:56 AM Ayende Rahien

I'll let you guess what I am working on...


Gravatar

# re: Why I don't like FireBird: Part II 8/8/2008 1:45 AM Ro

Could it be a bug in your code? If I understand correctly, in the exception catch you only test for one message, for other messages control falls through to done = true and ends the loop, returning the data that was read in the first select command.


Gravatar

# re: Why I don't like FireBird: Part II 8/8/2008 6:18 PM Ayende Rahien

The code isn't the best, but the error is real

Comments have been closed on this topic.