I'm writing a daemon to monitor creation of new objects, which adds rows to a database table when it detects new things. We'll call the objects widgets. The code flow is approximately this:
1: every so often:
2: find newest N widgets (from external source)
3: foreach widget
4: if( widget not yet in database )
5: add rows for widget
The last two lines present a race condition, since if two instances of this daemon are running at the same time, they may both create a row for widget X if the timing lines up.
The most obvious solution would be to use a unique
constraint on the widget identifier column, but that isn't possible due to the database layout (it's actually allowed to have more than one row for a widget, but the daemon shouldn't ever do this automatically).
My next thought would be to use a transaction, since this is what they're intended for. In the ADO.NET world, I believe I would want an isolation level of Serializable
, but I'm not positive. Can someone point me in the right direction?
Update: I did some experimentation, and the Serialized transaction doesn't appear to resolve the issue, or at least not very well. The interesting case is described below, and assumes that only one table is involved. Note that I'm not positive about the lock details, but I think I have it right:
Thread A: Executes line 4, acquiring a read lock on the table
Thread B: Executes line 4, acquiring a read lock on the table
Thread A: Tries to execute line 5, which requires upgrading to a write lock
(this requires waiting until Thread B unlocks the table)
Thread B: Tries to execute line 5, again requiring a lock upgrade
(this requires waiting until Thread A unlocks)
This leaves us in a classic deadlock condition. Other code paths are possible, but if threads A and B don't interleave, there's no synchronization issue anyway. The end result is that a SqlException is thrown on one of the threads, after SQL detects the deadlock and terminates one of the statements. I can catch this exception and detect the particular error code, but that doesn't feel very clean.
Another route I may take is to create a second table that tracks widgets seen by the daemon, where I can use a unique
constraint. This still requires catching and detecting certain error codes (in this case, integrity constraint violations), so I'm still interested in a better solution, if somebody can think of one.