Re: [sqlite] Race condition -- fixed?

2007-10-29 Thread Trevor Talbot
I wrote:

> I would still perform rollbacks for any errors other than the above
> expected SQLITE_BUSY cases, of course, since they indicate something
> else went wrong (such as running out of disk space).  I think it's
> safe to say those are all unusual cases though.

Hmm, http://sqlite.org/lang_transaction.html suggests even SQLITE_BUSY
might result in automatic transaction rollback.  sqlite3_step()
suggests the same thing.

Other docs suggest that SQLITE_IOERR_BLOCKED is actually returned in
such cases (like a writing transaction failing a cache spill attempt).

Which is correct?  Silently rolling back a transaction automatically
is bad enough, but doing that on SQLITE_BUSY would be just plain evil.

-
To unsubscribe, send email to [EMAIL PROTECTED]
-



Re: [sqlite] Race condition -- fixed?

2007-10-29 Thread Trevor Talbot
On 10/29/07, Richard Klein <[EMAIL PROTECTED]> wrote:

> Perhaps the best solution is to follow these rules:
>
> IF THE TRANSACTION IS A WRITER (i.e. will eventually write to the db):
> --
> (1) Begin the transaction with 'BEGIN IMMEDIATE'.  If you get SQLITE_
> BUSY, it means that another writer is already accessing the db.  Just
> sleep awhile and retry, as many times as necessary.  Once you get
> SQLITE_OK, you're the only writer accessing the db.
>
> (2) If you get SQLITE_BUSY later in the transaction when you want to
> write your cache to disk, it can only be due to one or more readers
> holding SHARED locks (because you're the only writer).  They will
> eventually exit without trying to acquire any stronger locks (again,
> because you're the only writer).  Therefore, no deadlock is possible.
> Just sleep awhile and retry, as many times as necessary.
>
> IF THE TRANSACTION IS A READER (i.e. will only read from the db):
> -
> (1) Begin the transaction with a simple 'BEGIN'.  This starts the
> transaction in the UNLOCKED state, so you *cannot* get a SQLITE_
> BUSY at this point.
>
> (2) If you get SQLITE_BUSY later in the transaction, it can only be
> due to your trying to acquire a SHARED lock while some other process
> holds a PENDING or EXCLUSIVE lock.  That process will eventually
> finish its writing and exit.  Therefore, no deadlock is possible.
> Just sleep awhile and retry, as many times as necessary.
>
>
> If all transactions follow the above rules, then explicit rollbacks
> should never be necessary.

For avoiding deadlocks, yes.  It can also be abstracted so the rest of
your code works on just "read-only" and "read/write" transactions,
which is convenient.

I would still perform rollbacks for any errors other than the above
expected SQLITE_BUSY cases, of course, since they indicate something
else went wrong (such as running out of disk space).  I think it's
safe to say those are all unusual cases though.

-
To unsubscribe, send email to [EMAIL PROTECTED]
-



Re: [sqlite] Race condition -- fixed?

2007-10-29 Thread Richard Klein

Trevor Talbot wrote:

On 10/29/07, Richard Klein <[EMAIL PROTECTED]> wrote:


But am I correct in assuming that one way that SQLite provides
serializable transactions is by automatically rolling back
transactions when necessary (and returning SQLITE_IOERR)?


No.  That will happen in some specific unavoidable cases, but you
cannot assume it will happen all of the time.


I assume you are referring to the case discussed in the article:

http://www.sqlite.org/cvstrac/wiki?p=CorruptionFollowingBusyError

i.e. the case where a non-COMMIT command within an explicit trans-
action fails when the executing process is unable to spill its cache
to disk due to a SQLITE_BUSY error, resulting in an inconsistent cache.


What will most likely
happen instead is that the first INSERT or UPDATE in a transaction
will return SQLITE_BUSY, and you will have to roll back the
transaction yourself.

It's also possible to simply keep
retrying a failing statement until it succeeds, but in the case of
INSERT or UPDATE that may lead to deadlock, as two connections that
want to write cannot proceed until one of them yields.


Perhaps the best solution is to follow these rules:

IF THE TRANSACTION IS A WRITER (i.e. will eventually write to the db):
--
(1) Begin the transaction with 'BEGIN IMMEDIATE'.  If you get SQLITE_
BUSY, it means that another writer is already accessing the db.  Just
sleep awhile and retry, as many times as necessary.  Once you get
SQLITE_OK, you're the only writer accessing the db.

(2) If you get SQLITE_BUSY later in the transaction when you want to
write your cache to disk, it can only be due to one or more readers
holding SHARED locks (because you're the only writer).  They will
eventually exit without trying to acquire any stronger locks (again,
because you're the only writer).  Therefore, no deadlock is possible.
Just sleep awhile and retry, as many times as necessary.

IF THE TRANSACTION IS A READER (i.e. will only read from the db):
-
(1) Begin the transaction with a simple 'BEGIN'.  This starts the
transaction in the UNLOCKED state, so you *cannot* get a SQLITE_
BUSY at this point.

(2) If you get SQLITE_BUSY later in the transaction, it can only be
due to your trying to acquire a SHARED lock while some other process
holds a PENDING or EXCLUSIVE lock.  That process will eventually
finish its writing and exit.  Therefore, no deadlock is possible.
Just sleep awhile and retry, as many times as necessary.


If all transactions follow the above rules, then explicit rollbacks
should never be necessary.

- Richard Klein

-
To unsubscribe, send email to [EMAIL PROTECTED]
-

Re: [sqlite] Race condition -- fixed?

2007-10-29 Thread Trevor Talbot
On 10/29/07, Richard Klein <[EMAIL PROTECTED]> wrote:

> But am I correct in assuming that one way that SQLite provides
> serializable transactions is by automatically rolling back
> transactions when necessary (and returning SQLITE_IOERR)?

No.  That will happen in some specific unavoidable cases, but you
cannot assume it will happen all of the time.  What will most likely
happen instead is that the first INSERT or UPDATE in a transaction
will return SQLITE_BUSY, and you will have to roll back the
transaction yourself.

It's possible to continue within a transaction by ignoring an error
from an individual statement (depending on the nature of the error),
is why I make this distinction.  It's also possible to simply keep
retrying a failing statement until it succeeds, but in the case of
INSERT or UPDATE that may lead to deadlock, as two connections that
want to write cannot proceed until one of them yields.

On the other hand, if you get SQLITE_BUSY on COMMIT, you probably want
to retry it, as it's just waiting for other readers to go away first.
Automatically rolling back then would be bad :)

-
To unsubscribe, send email to [EMAIL PROTECTED]
-



Re: [sqlite] Race condition -- fixed?

2007-10-29 Thread Ken
Its up to you to rollback the transaction.
It would return a SQLITE_BUSY, not an IOERR.



Richard Klein <[EMAIL PROTECTED]> wrote: But am I correct in assuming that one 
way that SQLite provides
serializable transactions is by automatically rolling back
transactions when necessary (and returning SQLITE_IOERR)?

Thanks,
- Richard Klein


[EMAIL PROTECTED] wrote:
> Ken  wrote:
>> BEGIN TRANSACTION;
>> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
>>  UPDATE accounts SET balance = 
>>   WHERE accountId = '123-45-6789';
>> COMMIT;
>>
>> This is a comman and naive assumption that the balance selected 
>> will remain consistent.
>>
> 
> Actually, SQLite does provide this guarantee.  Nothing in
> the database will change during a transaction, except for
> changes caused by INSERT, UPDATE, and DELETE statements
> that occur within the transaction itself.  It is not
> possible for another process to modify the value of the
> "balance" in between the SELECT and the UPDATE in the
> SQL above.
> 
> This is true of SQLite because isolation in SQLite
> is "SERIALIZABLE".  This is the highest level of isolate
> provided by SQL.  Most client/server database engines
> by default implement "READ COMMITTED".  The value of
> "balance" might change between the SELECT and the
> UPDATE in MySQL, for example.  (I'm less clear about
> what happens in PostgreSQL and Oracle.  The point is
> that your mileage may vary so be cautious.)
> 
> But SQLite gets this right.  Transactions are fully
> serializable, which means they appear as if the entire
> transaction happens instanteously with no chance for
> outside processes to change values in the middle of a
> transaction.
> 
> --
> D. Richard Hipp 
> 
> 
> -
> To unsubscribe, send email to [EMAIL PROTECTED]
> -
> 

-
To unsubscribe, send email to [EMAIL PROTECTED]
-


Re: [sqlite] Race condition -- fixed?

2007-10-29 Thread Richard Klein

But am I correct in assuming that one way that SQLite provides
serializable transactions is by automatically rolling back
transactions when necessary (and returning SQLITE_IOERR)?

Thanks,
- Richard Klein


[EMAIL PROTECTED] wrote:

Ken <[EMAIL PROTECTED]> wrote:

BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
 UPDATE accounts SET balance = 
  WHERE accountId = '123-45-6789';
COMMIT;

This is a comman and naive assumption that the balance selected 
will remain consistent.




Actually, SQLite does provide this guarantee.  Nothing in
the database will change during a transaction, except for
changes caused by INSERT, UPDATE, and DELETE statements
that occur within the transaction itself.  It is not
possible for another process to modify the value of the
"balance" in between the SELECT and the UPDATE in the
SQL above.

This is true of SQLite because isolation in SQLite
is "SERIALIZABLE".  This is the highest level of isolate
provided by SQL.  Most client/server database engines
by default implement "READ COMMITTED".  The value of
"balance" might change between the SELECT and the
UPDATE in MySQL, for example.  (I'm less clear about
what happens in PostgreSQL and Oracle.  The point is
that your mileage may vary so be cautious.)

But SQLite gets this right.  Transactions are fully
serializable, which means they appear as if the entire
transaction happens instanteously with no chance for
outside processes to change values in the middle of a
transaction.

--
D. Richard Hipp <[EMAIL PROTECTED]>


-
To unsubscribe, send email to [EMAIL PROTECTED]
-



-
To unsubscribe, send email to [EMAIL PROTECTED]
-

Re: [sqlite] Race condition -- fixed?

2007-10-27 Thread Florian Weimer
> This is true of SQLite because isolation in SQLite
> is "SERIALIZABLE".  This is the highest level of isolate
> provided by SQL.  Most client/server database engines
> by default implement "READ COMMITTED".  The value of
> "balance" might change between the SELECT and the
> UPDATE in MySQL, for example.  (I'm less clear about
> what happens in PostgreSQL and Oracle.  The point is
> that your mileage may vary so be cautious.)

PostgreSQL uses "READ COMMITTED" by default as well (each statement
acquires a new snapshot).

However, PostgreSQL does not implement true SERIALIZABLE semantics
because of the phantom problem (transaction outcome depends on the
absence of certain rows--but no lock can be acquired on them, so they
might be added by a parallel transaction, resulting in a
non-serializable history).

-
To unsubscribe, send email to [EMAIL PROTECTED]
-



Re: [sqlite] Race condition -- fixed?

2007-10-26 Thread Trevor Talbot
On 10/26/07, [EMAIL PROTECTED] <[EMAIL PROTECTED]> wrote:

> This is true of SQLite because isolation in SQLite
> is "SERIALIZABLE".  This is the highest level of isolate
> provided by SQL.  Most client/server database engines
> by default implement "READ COMMITTED".  The value of
> "balance" might change between the SELECT and the
> UPDATE in MySQL, for example.  (I'm less clear about
> what happens in PostgreSQL and Oracle.  The point ism
> that your mileage may vary so be cautious.)

PostgreSQL supports both READ COMMITTED and SERIALIZABLE, but the
out-of-box default is READ COMMITTED.  It can be selected
per-transaction, or changed as a default for all transactions.  I
believe Oracle is similar.

MySQL is a little weird due to different storage engines. From what I
can tell, MyISAM doesn't actually support transactions, so you
essentially get READ UNCOMMITTED if you try; InnoDB supports all 4
levels and defaults to REPEATABLE READ.

The differences between MVCC (Multi-Version Concurrency Control) and
locking -based implementations can also show up in corner cases, so
it's something you definitely want to look at whenever examing a
database engine.

-
To unsubscribe, send email to [EMAIL PROTECTED]
-



Re: [sqlite] Race condition -- fixed?

2007-10-26 Thread Ken
Richard:

Actually No, process A will not acquire the reserved lock. It failes returning 
a sqlite_busy, and must perform a rollback. Even if Process B commits. Process 
A will get a sqlite_busy, forcing you to rollback.

In order for the application to be correct the entire transaction must be 
restarted. The select would be run again, returning process B's correct value 
of (10,000 + 1000) = 11,000 ; 

A simpler/cleaner implementation might be:
   begin immediate   --- Busy waiting would occur here. ie sqlite busy is 
returned.
 select 
 update
  commit;

Ken

 Ken <[EMAIL PROTECTED]> wrote: 

Richard Klein  wrote: Dan Kennedy wrote:
> On Wed, 2007-10-24 at 21:38 -0700, Richard Klein wrote:
>> As I was thinking about the locking mechanism in SQLite 3,
>> it occurred to me that the following race condition could
>> occur.
>>
>> Imagine a joint bank account with a balance of $10,000.
>> The wife makes a withdrawal of $1,000 at ATM 'A' (serviced
>> by process A in the bank's mainframe), while at the same
>> time the husband makes a deposit of $1,000 at ATM 'B'
>> (serviced by process B).  The steps performed by each
>> process are as follows:
>>
>> Process A
>> -
>> BEGIN TRANSACTION;
>> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
>> UPDATE accounts SET balance = 
>>  WHERE accountId = '123-45-6789';
>> COMMIT;
>>
>> Process B
>> -
>> BEGIN TRANSACTION;
>> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
>> UPDATE accounts SET balance = 
>>  WHERE accountId = '123-45-6789';
>> COMMIT;
>>
>> Both processes open the accounts database, obtain SHARED
>> locks, and proceed at about the same pace.  Process A
>> updates her local cache with a new balance of $900, while
>> process B updates his local cache with a new balance of
>> $11,000.
> 
> Evaluating the UPDATE requires a RESERVED lock on the database 
> file. Since only one process can hold the RESERVED lock, this
> particular scenario cannot occur. One or other of the updates
> will fail with SQLITE_BUSY.
> 
> Dan.

I went over the documentation again, and it appears that you
are correct:  A process wanting to do an UPDATE must first
acquire a RESERVED lock.

However, I believe there is still a race condition.  Assume
that process B is the first to reach the UPDATE statement,
and so process B gets the RESERVED lock (which he later
escalates to PENDING and EXCLUSIVE).  Then process A is
stuck in a busy wait until process B commits and releases
his locks.  At this process A acquires the RESERVED lock
and does *her* UPDATE, but she does it using the old, now
*stale*, value of the account balance ($10,000).  That is,
she computes the new balance as ($10,000 - $1,000) = $9,000,
which is *wrong*.

She needs to *re-execute* her SELECT statement to pick up
the new balance of $11,000.  Or, in general:

She needs to start her transaction over from the beginning.

- Richard Klein

-
To unsubscribe, send email to [EMAIL PROTECTED]
-
I submit that there is no race condition present. Merely a progamming error.

BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
 UPDATE accounts SET balance = 
  WHERE accountId = '123-45-6789';
COMMIT;

This is a comman and naive assumption that the balance selected will remain
consistent. Even in other DB's such as oracle the problem described would 
persist.

Two my knowledge there are two solutions.
1: Don't programatically put the balance into a variable.
BEGIN TRANSACTION;
 SELECT balance FROM accounts WHERE accountId = '123-45-6789';
 UPDATE accounts SET balance = balance - 
  WHERE accountId = '123-45-6789';
COMMIT;

2:  Lock the row with the select statement, this requires
additional syntax, which sqlite does not support.
BEGIN TRANSACTION;
 SELECT balance FOR UPDATE 
   FROM accounts WHERE accountId = '123-45-6789';

 UPDATE accounts SET balance = 
  WHERE accountId = '123-45-6789';
COMMIT;




 





Re: [sqlite] Race condition -- fixed?

2007-10-26 Thread Ken

That makes sense given SERIALIZABLE transactions. 

The entire trasaction will require rollback since it gets a sqlite_busy, Not 
just the update. This also has the effect of invalidating the data selected 
within the transaction that performs the rollback.

Oracle defaults to Read Committed. But you may set a transaction to 
serailazable as well. 

Sorry for my confusion and thanks for the clarification.
Ken

 Ken  wrote:
> 
> BEGIN TRANSACTION;
> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
>  UPDATE accounts SET balance = 
>   WHERE accountId = '123-45-6789';
> COMMIT;
> 
> This is a comman and naive assumption that the balance selected 
> will remain consistent.
> 

Actually, SQLite does provide this guarantee.  Nothing in
the database will change during a transaction, except for
changes caused by INSERT, UPDATE, and DELETE statements
that occur within the transaction itself.  It is not
possible for another process to modify the value of the
"balance" in between the SELECT and the UPDATE in the
SQL above.

This is true of SQLite because isolation in SQLite
is "SERIALIZABLE".  This is the highest level of isolate
provided by SQL.  Most client/server database engines
by default implement "READ COMMITTED".  The value of
"balance" might change between the SELECT and the
UPDATE in MySQL, for example.  (I'm less clear about
what happens in PostgreSQL and Oracle.  The point is
that your mileage may vary so be cautious.)

But SQLite gets this right.  Transactions are fully
serializable, which means they appear as if the entire
transaction happens instanteously with no chance for
outside processes to change values in the middle of a
transaction.

--
D. Richard Hipp 


-
To unsubscribe, send email to [EMAIL PROTECTED]
-




Re: [sqlite] Race condition -- fixed?

2007-10-26 Thread drh
Ken <[EMAIL PROTECTED]> wrote:
> 
> BEGIN TRANSACTION;
> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
>  UPDATE accounts SET balance = 
>   WHERE accountId = '123-45-6789';
> COMMIT;
> 
> This is a comman and naive assumption that the balance selected 
> will remain consistent.
> 

Actually, SQLite does provide this guarantee.  Nothing in
the database will change during a transaction, except for
changes caused by INSERT, UPDATE, and DELETE statements
that occur within the transaction itself.  It is not
possible for another process to modify the value of the
"balance" in between the SELECT and the UPDATE in the
SQL above.

This is true of SQLite because isolation in SQLite
is "SERIALIZABLE".  This is the highest level of isolate
provided by SQL.  Most client/server database engines
by default implement "READ COMMITTED".  The value of
"balance" might change between the SELECT and the
UPDATE in MySQL, for example.  (I'm less clear about
what happens in PostgreSQL and Oracle.  The point is
that your mileage may vary so be cautious.)

But SQLite gets this right.  Transactions are fully
serializable, which means they appear as if the entire
transaction happens instanteously with no chance for
outside processes to change values in the middle of a
transaction.

--
D. Richard Hipp <[EMAIL PROTECTED]>


-
To unsubscribe, send email to [EMAIL PROTECTED]
-



Re: [sqlite] Race condition -- fixed?

2007-10-26 Thread Ken


Richard Klein <[EMAIL PROTECTED]> wrote: Dan Kennedy wrote:
> On Wed, 2007-10-24 at 21:38 -0700, Richard Klein wrote:
>> As I was thinking about the locking mechanism in SQLite 3,
>> it occurred to me that the following race condition could
>> occur.
>>
>> Imagine a joint bank account with a balance of $10,000.
>> The wife makes a withdrawal of $1,000 at ATM 'A' (serviced
>> by process A in the bank's mainframe), while at the same
>> time the husband makes a deposit of $1,000 at ATM 'B'
>> (serviced by process B).  The steps performed by each
>> process are as follows:
>>
>> Process A
>> -
>> BEGIN TRANSACTION;
>> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
>> UPDATE accounts SET balance = 
>>  WHERE accountId = '123-45-6789';
>> COMMIT;
>>
>> Process B
>> -
>> BEGIN TRANSACTION;
>> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
>> UPDATE accounts SET balance = 
>>  WHERE accountId = '123-45-6789';
>> COMMIT;
>>
>> Both processes open the accounts database, obtain SHARED
>> locks, and proceed at about the same pace.  Process A
>> updates her local cache with a new balance of $900, while
>> process B updates his local cache with a new balance of
>> $11,000.
> 
> Evaluating the UPDATE requires a RESERVED lock on the database 
> file. Since only one process can hold the RESERVED lock, this
> particular scenario cannot occur. One or other of the updates
> will fail with SQLITE_BUSY.
> 
> Dan.

I went over the documentation again, and it appears that you
are correct:  A process wanting to do an UPDATE must first
acquire a RESERVED lock.

However, I believe there is still a race condition.  Assume
that process B is the first to reach the UPDATE statement,
and so process B gets the RESERVED lock (which he later
escalates to PENDING and EXCLUSIVE).  Then process A is
stuck in a busy wait until process B commits and releases
his locks.  At this process A acquires the RESERVED lock
and does *her* UPDATE, but she does it using the old, now
*stale*, value of the account balance ($10,000).  That is,
she computes the new balance as ($10,000 - $1,000) = $9,000,
which is *wrong*.

She needs to *re-execute* her SELECT statement to pick up
the new balance of $11,000.  Or, in general:

She needs to start her transaction over from the beginning.

- Richard Klein

-
To unsubscribe, send email to [EMAIL PROTECTED]
-
I submit that there is no race condition present. Merely a progamming error.

BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
 UPDATE accounts SET balance = 
  WHERE accountId = '123-45-6789';
COMMIT;

This is a comman and naive assumption that the balance selected will remain
consistent. Even in other DB's such as oracle the problem described would 
persist.

Two my knowledge there are two solutions.
1: Don't programatically put the balance into a variable.
BEGIN TRANSACTION;
 SELECT balance FROM accounts WHERE accountId = '123-45-6789';
 UPDATE accounts SET balance = balance - 
  WHERE accountId = '123-45-6789';
COMMIT;

2:  Lock the row with the select statement, this requires
additional syntax, which sqlite does not support.
BEGIN TRANSACTION;
 SELECT balance FOR UPDATE 
   FROM accounts WHERE accountId = '123-45-6789';

 UPDATE accounts SET balance = 
  WHERE accountId = '123-45-6789';
COMMIT;




 




Re: [sqlite] Race condition -- fixed?

2007-10-25 Thread Richard Klein

Trevor Talbot wrote:

On 10/25/07, Richard Klein <[EMAIL PROTECTED]> wrote:

Trevor Talbot wrote:

The thing is, SQLite's synchronization mechanism is simpler than most
full-featured SQL databases.  In all cases, the point is that if you
are attempting to do simultaneous writes from two connections, each
connection must be prepared to receive an error, rollback the
transaction, and try again.  Personally, I don't see anything terribly

 > complicated about that.

It's not complicated at all.  It's just an aspect of database programming
that I was unaware of, until now (I'm a database newbie).


You're also implementing SQLite's locking system for your own
platform, so you need to be concerned with these details anyway.
You're not just blindly implementing it, you're also verifying your
understanding of the goals and taking the time to question SQLite's
behavior to make sure everything is correct.  I think that's
absolutely wonderful.


Thank you for understanding that!   :-)

- Richard Klein


-
To unsubscribe, send email to [EMAIL PROTECTED]
-

Re: [sqlite] Race condition -- fixed?

2007-10-25 Thread Trevor Talbot
On 10/25/07, Richard Klein <[EMAIL PROTECTED]> wrote:
> Trevor Talbot wrote:
> > The thing is, SQLite's synchronization mechanism is simpler than most
> > full-featured SQL databases.  In all cases, the point is that if you
> > are attempting to do simultaneous writes from two connections, each
> > connection must be prepared to receive an error, rollback the
> > transaction, and try again.  Personally, I don't see anything terribly
>  > complicated about that.
>
> It's not complicated at all.  It's just an aspect of database programming
> that I was unaware of, until now (I'm a database newbie).

You're also implementing SQLite's locking system for your own
platform, so you need to be concerned with these details anyway.
You're not just blindly implementing it, you're also verifying your
understanding of the goals and taking the time to question SQLite's
behavior to make sure everything is correct.  I think that's
absolutely wonderful.

I just wanted to point out to anyone seeing this thread and thinking
"eek" that from the perspective of using SQLite one doesn't need to be
concerned with these details, because the original implementors, and
people like yourself, keep verifying those details do what they're
supposed to do.

-
To unsubscribe, send email to [EMAIL PROTECTED]
-



Re: [sqlite] Race condition -- fixed?

2007-10-25 Thread John Stanton
I am sure that you are correct, that Sqlite's sync mechanism is not 
terribly complicated for you and for anyone else who understands the 
principles,  however it does confuse many users as you see from the 
posts to this forum.  Simple to use could become simpler to use.


Synchronizing transactions is not intricate,  particularly when it is 
achieved transparently to the application programmer by the Sqlite 
application support layer.


Trevor Talbot wrote:

The thing is, SQLite's synchronization mechanism is simpler than most
full-featured SQL databases.  In all cases, the point is that if you
are attempting to do simultaneous writes from two connections, each
connection must be prepared to receive an error, rollback the
transaction, and try again.

Personally, I don't see anything terribly complicated about that.
Concerning yourself with the intricate details in order to get the
best performance is complicated, sure, but the basic principle isn't.
You have to do error checking anyway, the only difference is that it
may affect your entire transaction rather than just one statement.
That's true for working with SQL databases in general.

For those applications don't want parallel access at all, SQLite
provides BEGIN EXCLUSIVE.  Adding your own mechanisms on top just
means another thing for you to test independently, which seems like
adding complexity.

I do recognize that none of the above necessarily applies to specific
environments, where you need to do your own thing anyway.  I'm just
commenting that in general, SQLite is already simple to use.

-
To unsubscribe, send email to [EMAIL PROTECTED]
-

  



-
To unsubscribe, send email to [EMAIL PROTECTED]
-



Re: [sqlite] Race condition -- fixed?

2007-10-25 Thread Richard Klein

Dan Kennedy wrote:

On Wed, 2007-10-24 at 21:38 -0700, Richard Klein wrote:

As I was thinking about the locking mechanism in SQLite 3,
it occurred to me that the following race condition could
occur.

Imagine a joint bank account with a balance of $10,000.
The wife makes a withdrawal of $1,000 at ATM 'A' (serviced
by process A in the bank's mainframe), while at the same
time the husband makes a deposit of $1,000 at ATM 'B'
(serviced by process B).  The steps performed by each
process are as follows:

Process A
-
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
UPDATE accounts SET balance = 
 WHERE accountId = '123-45-6789';
COMMIT;

Process B
-
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
UPDATE accounts SET balance = 
 WHERE accountId = '123-45-6789';
COMMIT;

Both processes open the accounts database, obtain SHARED
locks, and proceed at about the same pace.  Process A
updates her local cache with a new balance of $900, while
process B updates his local cache with a new balance of
$11,000.


Evaluating the UPDATE requires a RESERVED lock on the database 
file. Since only one process can hold the RESERVED lock, this

particular scenario cannot occur. One or other of the updates
will fail with SQLITE_BUSY.

Dan.


I went over the documentation again, and it appears that you
are correct:  A process wanting to do an UPDATE must first
acquire a RESERVED lock.

However, I believe there is still a race condition.  Assume
that process B is the first to reach the UPDATE statement,
and so process B gets the RESERVED lock (which he later
escalates to PENDING and EXCLUSIVE).  Then process A is
stuck in a busy wait until process B commits and releases
his locks.  At this process A acquires the RESERVED lock
and does *her* UPDATE, but she does it using the old, now
*stale*, value of the account balance ($10,000).  That is,
she computes the new balance as ($10,000 - $1,000) = $9,000,
which is *wrong*.

She needs to *re-execute* her SELECT statement to pick up
the new balance of $11,000.  Or, in general:

She needs to start her transaction over from the beginning.

- Richard Klein

-
To unsubscribe, send email to [EMAIL PROTECTED]
-

Re: [sqlite] Race condition -- fixed?

2007-10-25 Thread Richard Klein

Trevor Talbot wrote:

The thing is, SQLite's synchronization mechanism is simpler than most
full-featured SQL databases.  In all cases, the point is that if you
are attempting to do simultaneous writes from two connections, each
connection must be prepared to receive an error, rollback the
transaction, and try again.  Personally, I don't see anything terribly

> complicated about that.

It's not complicated at all.  It's just an aspect of database programming
that I was unaware of, until now (I'm a database newbie).


For those applications don't want parallel access at all, SQLite
provides BEGIN EXCLUSIVE.


That's true.  If you begin your transaction with BEGIN EXCLUSIVE, then
it cannot be interrupted by another transaction wishing to write to the
database, so rollback won't be an issue.

- Richard Klein


-
To unsubscribe, send email to [EMAIL PROTECTED]
-

Re: [sqlite] Race condition -- fixed?

2007-10-25 Thread Richard Klein

Lee Crain wrote:

I've wrapped all of my company's SQLite database accesses in my own API
layer that encapsulates all of our applications' business rules and forces
ALL transactions, no matter how lengthy or trivial, to be atomic by using
a MUTEX to avoid the types of scenarios described below. This includes
simple database accesses, even reads.


I think that's a perfectly acceptable solution, if you can live with the
performance hit (and it sounds like you can).

- Richard Klein


-
To unsubscribe, send email to [EMAIL PROTECTED]
-

Re: [sqlite] Race condition -- fixed?

2007-10-25 Thread Trevor Talbot
The thing is, SQLite's synchronization mechanism is simpler than most
full-featured SQL databases.  In all cases, the point is that if you
are attempting to do simultaneous writes from two connections, each
connection must be prepared to receive an error, rollback the
transaction, and try again.

Personally, I don't see anything terribly complicated about that.
Concerning yourself with the intricate details in order to get the
best performance is complicated, sure, but the basic principle isn't.
You have to do error checking anyway, the only difference is that it
may affect your entire transaction rather than just one statement.
That's true for working with SQL databases in general.

For those applications don't want parallel access at all, SQLite
provides BEGIN EXCLUSIVE.  Adding your own mechanisms on top just
means another thing for you to test independently, which seems like
adding complexity.

I do recognize that none of the above necessarily applies to specific
environments, where you need to do your own thing anyway.  I'm just
commenting that in general, SQLite is already simple to use.

-
To unsubscribe, send email to [EMAIL PROTECTED]
-



Re: [sqlite] Race condition -- fixed?

2007-10-25 Thread John Stanton
You make a sound point.  From my perspective the Sqlite synchronization 
mechanisms are a flawed part of an otherwise elegantly simple design, as 
reading this forum indicates.  Synchronization problems are the major 
item of confusion among users.  A more robust and less intricate 
interface would be a productivity improver.


Follow Einstein - "Make it as simple as possible, but no simpler".

We use the mutex approach and are rewarded with simpler logic, no ugly 
busy waits or wasteful pauses, more confidence when deploying 
applications and no 3AM calls.  A sophistication is to use read locks  
where appropriate.  A read (shared) lock can be easily contrived from 
the Windows sync primitives and exists with pthreads.


Threaded programs are here to stay, so the synchronization method would 
be better built from threads up instead of at process level with 
threading support overlaid later.


Lee Crain wrote:

Guys,

I read this forum regularly and I've given a lot of thought to all of
these conversations about low level logic and trying to squeeze the last
ounce of performance out of SQLite.

That's not for me. Simplicity equates to robustness and my company needs
robustness. And my time is really expensive so I need to not spend too
much time tweaking my SQLite based application to get everything to work
reliably and efficiently. 


I've wrapped all of my company's SQLite database accesses in my own API
layer that encapsulates all of our applications' business rules and forces
ALL transactions, no matter how lengthy or trivial, to be atomic by using
a MUTEX to avoid the types of scenarios described below. This includes
simple database accesses, even reads.

Yes, I know it makes things slower. But, it's solid. And I won't be
getting calls at 3:00 a.m. about some mysterious database problem. 


If my company wants something faster, they need to buy SQL Server. At
$5000, it's a bargain. At my salary, I'm not.

My 2 cents,

Lee Crain

_


-Original Message-
From: Richard Klein [mailto:[EMAIL PROTECTED] 
Sent: Wednesday, October 24, 2007 10:39 PM

To: sqlite-users@sqlite.org
Subject: [sqlite] Race condition -- fixed?

As I was thinking about the locking mechanism in SQLite 3,
it occurred to me that the following race condition could
occur.

Imagine a joint bank account with a balance of $10,000.
The wife makes a withdrawal of $1,000 at ATM 'A' (serviced
by process A in the bank's mainframe), while at the same
time the husband makes a deposit of $1,000 at ATM 'B'
(serviced by process B).  The steps performed by each
process are as follows:

Process A
-
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
UPDATE accounts SET balance = 
 WHERE accountId = '123-45-6789';
COMMIT;

Process B
-
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
UPDATE accounts SET balance = 
 WHERE accountId = '123-45-6789';
COMMIT;

Both processes open the accounts database, obtain SHARED
locks, and proceed at about the same pace.  Process A
updates her local cache with a new balance of $900, while
process B updates his local cache with a new balance of
$11,000.

Now suppose B gets to the COMMIT first.  He tries to get a
PENDING lock and succeeds.  He then tries to promote his
PENDING lock to EXCLUSIVE, but gets a SQLITE_BUSY instead,
because process A holds a SHARED lock.  So, he goes to sleep,
hoping that when he awakens the SHARED lock will be gone.

Meanwhile, process A reaches her COMMIT, tries to get a
PENDING lock, but gets a SQLITE_BUSY instead, because
process B already holds a PENDING lock.  Process A then
releases her SHARED lock (so that process B can be promoted
from PENDING to EXCLUSIVE and do his commit), and goes to
sleep, hoping that when she wakes up the PENDING lock will
be gone.

Process B then wakes up, finds the database UNLOCKED, obtains
his EXCLUSIVE lock, commits his local cache's balance of
$11,000 to the database, releases his lock, and exits.

Process A then wakes up, finds the database UNLOCKED, obtains
an EXCLUSIVE lock, commits her local cache's balance of $9,000
to the database, releases her lock, and exits.  *The database
now erroneously shows a balance of $9,000.*

The problem is that the moment that process B commits his local
cache's balance of $11,000 to the database, he causes process A's
local cache to become *stale*, i.e. inconsistent with the database.

After scouring the documentation, I came across the following
article:

http://www.sqlite.org/cvstrac/wiki?p=CorruptionFollowingBusyError

which seems to describe the exact scenario I described above.
According to this article, SQLite has been fixed so that if
a process encounters a SQLITE_BUSY during an explicit trans-
action, then the transaction will *automaticall

Re: [sqlite] Race condition -- fixed?

2007-10-25 Thread John Stanton
A classic solution to that problem is not to perform updates but to 
insert transactions,  The concept of log file systems to give 
concurrency is worth scrutiny.


Richard Klein wrote:

As I was thinking about the locking mechanism in SQLite 3,
it occurred to me that the following race condition could
occur.

Imagine a joint bank account with a balance of $10,000.
The wife makes a withdrawal of $1,000 at ATM 'A' (serviced
by process A in the bank's mainframe), while at the same
time the husband makes a deposit of $1,000 at ATM 'B'
(serviced by process B).  The steps performed by each
process are as follows:

Process A
-
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
UPDATE accounts SET balance = 
WHERE accountId = '123-45-6789';
COMMIT;

Process B
-
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
UPDATE accounts SET balance = 
WHERE accountId = '123-45-6789';
COMMIT;

Both processes open the accounts database, obtain SHARED
locks, and proceed at about the same pace.  Process A
updates her local cache with a new balance of $900, while
process B updates his local cache with a new balance of
$11,000.

Now suppose B gets to the COMMIT first.  He tries to get a
PENDING lock and succeeds.  He then tries to promote his
PENDING lock to EXCLUSIVE, but gets a SQLITE_BUSY instead,
because process A holds a SHARED lock.  So, he goes to sleep,
hoping that when he awakens the SHARED lock will be gone.

Meanwhile, process A reaches her COMMIT, tries to get a
PENDING lock, but gets a SQLITE_BUSY instead, because
process B already holds a PENDING lock.  Process A then
releases her SHARED lock (so that process B can be promoted
from PENDING to EXCLUSIVE and do his commit), and goes to
sleep, hoping that when she wakes up the PENDING lock will
be gone.

Process B then wakes up, finds the database UNLOCKED, obtains
his EXCLUSIVE lock, commits his local cache's balance of
$11,000 to the database, releases his lock, and exits.

Process A then wakes up, finds the database UNLOCKED, obtains
an EXCLUSIVE lock, commits her local cache's balance of $9,000
to the database, releases her lock, and exits.  *The database
now erroneously shows a balance of $9,000.*

The problem is that the moment that process B commits his local
cache's balance of $11,000 to the database, he causes process A's
local cache to become *stale*, i.e. inconsistent with the database.

After scouring the documentation, I came across the following
article:

http://www.sqlite.org/cvstrac/wiki?p=CorruptionFollowingBusyError

which seems to describe the exact scenario I described above.
According to this article, SQLite has been fixed so that if
a process encounters a SQLITE_BUSY during an explicit trans-
action, then the transaction will *automatically* be rolled
back, and the app will receive an error code of SQLITE_IOERR
*instead of* SQLITE_BUSY.

I understand this to mean that whenever coding an explicit
transaction, the programmer must always be prepared to receive
an SQLITE_IOERR when stepping through any SQL statement, and
must deal with this error by going back to the start of the
transaction and starting over.

- Richard Klein




-
To unsubscribe, send email to [EMAIL PROTECTED]
-



-
To unsubscribe, send email to [EMAIL PROTECTED]
-



RE: [sqlite] Race condition -- fixed?

2007-10-25 Thread Lee Crain
Guys,

I read this forum regularly and I've given a lot of thought to all of
these conversations about low level logic and trying to squeeze the last
ounce of performance out of SQLite.

That's not for me. Simplicity equates to robustness and my company needs
robustness. And my time is really expensive so I need to not spend too
much time tweaking my SQLite based application to get everything to work
reliably and efficiently. 

I've wrapped all of my company's SQLite database accesses in my own API
layer that encapsulates all of our applications' business rules and forces
ALL transactions, no matter how lengthy or trivial, to be atomic by using
a MUTEX to avoid the types of scenarios described below. This includes
simple database accesses, even reads.

Yes, I know it makes things slower. But, it's solid. And I won't be
getting calls at 3:00 a.m. about some mysterious database problem. 

If my company wants something faster, they need to buy SQL Server. At
$5000, it's a bargain. At my salary, I'm not.

My 2 cents,

Lee Crain

_


-Original Message-
From: Richard Klein [mailto:[EMAIL PROTECTED] 
Sent: Wednesday, October 24, 2007 10:39 PM
To: sqlite-users@sqlite.org
Subject: [sqlite] Race condition -- fixed?

As I was thinking about the locking mechanism in SQLite 3,
it occurred to me that the following race condition could
occur.

Imagine a joint bank account with a balance of $10,000.
The wife makes a withdrawal of $1,000 at ATM 'A' (serviced
by process A in the bank's mainframe), while at the same
time the husband makes a deposit of $1,000 at ATM 'B'
(serviced by process B).  The steps performed by each
process are as follows:

Process A
-
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
UPDATE accounts SET balance = 
 WHERE accountId = '123-45-6789';
COMMIT;

Process B
-
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
UPDATE accounts SET balance = 
 WHERE accountId = '123-45-6789';
COMMIT;

Both processes open the accounts database, obtain SHARED
locks, and proceed at about the same pace.  Process A
updates her local cache with a new balance of $900, while
process B updates his local cache with a new balance of
$11,000.

Now suppose B gets to the COMMIT first.  He tries to get a
PENDING lock and succeeds.  He then tries to promote his
PENDING lock to EXCLUSIVE, but gets a SQLITE_BUSY instead,
because process A holds a SHARED lock.  So, he goes to sleep,
hoping that when he awakens the SHARED lock will be gone.

Meanwhile, process A reaches her COMMIT, tries to get a
PENDING lock, but gets a SQLITE_BUSY instead, because
process B already holds a PENDING lock.  Process A then
releases her SHARED lock (so that process B can be promoted
from PENDING to EXCLUSIVE and do his commit), and goes to
sleep, hoping that when she wakes up the PENDING lock will
be gone.

Process B then wakes up, finds the database UNLOCKED, obtains
his EXCLUSIVE lock, commits his local cache's balance of
$11,000 to the database, releases his lock, and exits.

Process A then wakes up, finds the database UNLOCKED, obtains
an EXCLUSIVE lock, commits her local cache's balance of $9,000
to the database, releases her lock, and exits.  *The database
now erroneously shows a balance of $9,000.*

The problem is that the moment that process B commits his local
cache's balance of $11,000 to the database, he causes process A's
local cache to become *stale*, i.e. inconsistent with the database.

After scouring the documentation, I came across the following
article:

http://www.sqlite.org/cvstrac/wiki?p=CorruptionFollowingBusyError

which seems to describe the exact scenario I described above.
According to this article, SQLite has been fixed so that if
a process encounters a SQLITE_BUSY during an explicit trans-
action, then the transaction will *automatically* be rolled
back, and the app will receive an error code of SQLITE_IOERR
*instead of* SQLITE_BUSY.

I understand this to mean that whenever coding an explicit
transaction, the programmer must always be prepared to receive
an SQLITE_IOERR when stepping through any SQL statement, and
must deal with this error by going back to the start of the
transaction and starting over.

- Richard Klein




-
To unsubscribe, send email to [EMAIL PROTECTED]
-



Re: [sqlite] Race condition -- fixed?

2007-10-24 Thread Dan Kennedy
On Wed, 2007-10-24 at 21:38 -0700, Richard Klein wrote:
> As I was thinking about the locking mechanism in SQLite 3,
> it occurred to me that the following race condition could
> occur.
> 
> Imagine a joint bank account with a balance of $10,000.
> The wife makes a withdrawal of $1,000 at ATM 'A' (serviced
> by process A in the bank's mainframe), while at the same
> time the husband makes a deposit of $1,000 at ATM 'B'
> (serviced by process B).  The steps performed by each
> process are as follows:
> 
> Process A
> -
> BEGIN TRANSACTION;
> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
> UPDATE accounts SET balance = 
>  WHERE accountId = '123-45-6789';
> COMMIT;
> 
> Process B
> -
> BEGIN TRANSACTION;
> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
> UPDATE accounts SET balance = 
>  WHERE accountId = '123-45-6789';
> COMMIT;
>
> Both processes open the accounts database, obtain SHARED
> locks, and proceed at about the same pace.  Process A
> updates her local cache with a new balance of $900, while
> process B updates his local cache with a new balance of
> $11,000.

Evaluating the UPDATE requires a RESERVED lock on the database 
file. Since only one process can hold the RESERVED lock, this
particular scenario cannot occur. One or other of the updates
will fail with SQLITE_BUSY.

Dan.




-
To unsubscribe, send email to [EMAIL PROTECTED]
-



[sqlite] Race condition -- fixed?

2007-10-24 Thread Richard Klein

As I was thinking about the locking mechanism in SQLite 3,
it occurred to me that the following race condition could
occur.

Imagine a joint bank account with a balance of $10,000.
The wife makes a withdrawal of $1,000 at ATM 'A' (serviced
by process A in the bank's mainframe), while at the same
time the husband makes a deposit of $1,000 at ATM 'B'
(serviced by process B).  The steps performed by each
process are as follows:

Process A
-
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
UPDATE accounts SET balance = 
WHERE accountId = '123-45-6789';
COMMIT;

Process B
-
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
UPDATE accounts SET balance = 
WHERE accountId = '123-45-6789';
COMMIT;

Both processes open the accounts database, obtain SHARED
locks, and proceed at about the same pace.  Process A
updates her local cache with a new balance of $900, while
process B updates his local cache with a new balance of
$11,000.

Now suppose B gets to the COMMIT first.  He tries to get a
PENDING lock and succeeds.  He then tries to promote his
PENDING lock to EXCLUSIVE, but gets a SQLITE_BUSY instead,
because process A holds a SHARED lock.  So, he goes to sleep,
hoping that when he awakens the SHARED lock will be gone.

Meanwhile, process A reaches her COMMIT, tries to get a
PENDING lock, but gets a SQLITE_BUSY instead, because
process B already holds a PENDING lock.  Process A then
releases her SHARED lock (so that process B can be promoted
from PENDING to EXCLUSIVE and do his commit), and goes to
sleep, hoping that when she wakes up the PENDING lock will
be gone.

Process B then wakes up, finds the database UNLOCKED, obtains
his EXCLUSIVE lock, commits his local cache's balance of
$11,000 to the database, releases his lock, and exits.

Process A then wakes up, finds the database UNLOCKED, obtains
an EXCLUSIVE lock, commits her local cache's balance of $9,000
to the database, releases her lock, and exits.  *The database
now erroneously shows a balance of $9,000.*

The problem is that the moment that process B commits his local
cache's balance of $11,000 to the database, he causes process A's
local cache to become *stale*, i.e. inconsistent with the database.

After scouring the documentation, I came across the following
article:

http://www.sqlite.org/cvstrac/wiki?p=CorruptionFollowingBusyError

which seems to describe the exact scenario I described above.
According to this article, SQLite has been fixed so that if
a process encounters a SQLITE_BUSY during an explicit trans-
action, then the transaction will *automatically* be rolled
back, and the app will receive an error code of SQLITE_IOERR
*instead of* SQLITE_BUSY.

I understand this to mean that whenever coding an explicit
transaction, the programmer must always be prepared to receive
an SQLITE_IOERR when stepping through any SQL statement, and
must deal with this error by going back to the start of the
transaction and starting over.

- Richard Klein


-
To unsubscribe, send email to [EMAIL PROTECTED]
-