From what I can tell this is happening because I have different threads. The lock code ensures that only one thread at a time has a SQLiteConnection to the database. Each thread gets a new SQLiteConnection for each connection however I use Pooling=True; inside the connection string.
I ended up setting breakpoints inside sqlite3.c - function "winopen" and it has some code that if it tries to open the file readwrite and it fails then it tries to get it readonly and marks the DB accordingly. Now in theory this should never happen in my application as I always lock and always dispose of connections/transactions/commands/readers etc. (at least as far as I can tell - they are all inside "using" blocks). Windows is sometimes returning ERROR_SHARING_VIOLATION from the open file call which is what blows things up.
I was running filemon at the time and from what I could tell I saw my application open the file in question - then it did some stuff including locking and then it did an UNLOCK but didn't close it... later on another OPEN gave the sharing violation.
I've no idea yet why it didn't close the file that one time (like it usually does). Does Sqlite always open a file every time - I thought the idea was that connection pooling in the provider prevented that? Certainly I'm sure it ran a lot faster when I turned pooling on :). Ideally I'd like sqlite to keep the file open all the time - no one else should be allowed to touch it in my case - is there an option to do this?
For the record I've switched to SQLite from SQLCE (MS's Sql compact edition). SQLCE has better concurrency support (i.e. it can do at least table locking and maybe finer than that - so it doesn't lock the whole file) - but it's so much slower and I'm sure it wasn't even atomic and that transactions wouldn't rollback etc. With this provider it was easy to switch and it's so much faster and more robust :)