[Proposal] Add catch block to lock block #2613
Replies: 26 comments
-
This would be equivalent and works today. Although note that as with your code the lock would be released by the time you get to the lock (_obj) try
{
//code that needs to be synchronized
}
catch (Exception e)
{
//rolling back changes in case of error
} |
Beta Was this translation helpful? Give feedback.
-
It won't be because this would be compiled to: bool lockWasTaken = false;
var temp = obj;
try
{
Monitor.Enter(temp, ref lockWasTaken);
//code that needs to be synchronized
}
catch (Exception e)
{
//rolling back changes in case of error
}
finally
{
if (lockWasTaken)
Monitor.Exit(temp);
}
|
Beta Was this translation helpful? Give feedback.
-
@rev3r - A design that tolerates disruptive (to the thread safe updates that are being attempted) exceptions should be regarded as poor design IMHO. Unless the exception was caused by another thread (which itself should be impossible when holding the lock, unless its like ThreadAbortException) it will be caused by data accessed while holding the lock. That data is by definition shared and so would imply that every thread that tries to do the updates would encounter the exception. This means we'd get never ending lock-update-exception-rollback-unlock cycles over and over, never recovering, whenever any thread enters the lock. You could argue that an input argument might lead to an exception but in that case you'd validate arguments and throw before attempting to acquire the lock. Please suggest some types of exceptions you'd consider acceptable in the pattern you're suggesting. |
Beta Was this translation helpful? Give feedback.
-
@Korporal what if the exception was only one-time, and thrown in the middle of the That cycle would be only if |
Beta Was this translation helpful? Give feedback.
-
I'm arguing that such exception would likely not be one time. They would be caused by the data being accessed and so no thread could enter the code and avoid the exception once some other thread has encountered the exception and rolled back. By rolling back you set the state to its pre-exception state and thus garuantee that the next locking thread will hit the same exception. What kinds of exceptions might you get? Can you suggest an exception not caused by accessing shared state? |
Beta Was this translation helpful? Give feedback.
-
@Korporal Even if exception is not one time, the half-mutated resource problem will still be an issue. That's fixed with Imagine you need to change few fields in DB. Without rollback, exception would release the lock half-way through. Now each thread is free to read/write wrong state. And it's not only DB, everytime you need multiple steps in one Even C# language designer (Eric Lippert) and C# async concept designer (Jeffrey Richter) argues that it's a real issue with |
Beta Was this translation helpful? Give feedback.
-
@rev3r - But performing IO (e.g. Database) while holding a lock is itself regarded as poor design and so would be eliminated during a code review. By accessing a state S in such a way we generate an exception wholly dependent on S then by resetting the state back to S ("rolling back") we guarantee that the next thread to attempt this will generate the same exception, reset it and so on... In Windows for example (the rentrant kernel) if kernel code encounters such situations it "crashes" the system, that is to say it calls KeBugCheck() it does not attempt to rollback. Rolling back is no good because the problem was already present before the thread began its updates. What needs to be rolled back is the updates that caused the corruption not the updates made by code that later encounters the corruption. |
Beta Was this translation helpful? Give feedback.
-
@Korporal Even in-memory data structure manipulation can lead to exception. In my opinion better than releasing the lock prematurely is to allow crashing. So |
Beta Was this translation helpful? Give feedback.
-
@rev3r Yes, crashing might be best but in this case a lock block with a wholly contained try/catch is the ideal pattern, I see no advantage here in your proposed language change. |
Beta Was this translation helpful? Give feedback.
-
@Korporal It's the same as This means cleaner and little bit faster code. |
Beta Was this translation helpful? Give feedback.
-
It only removes a single keyword. @HaloFour's alternative seems to achieve the result you're after, with no language changes required. At the IL level you still have a double- |
Beta Was this translation helpful? Give feedback.
-
@rev3r - Well these are just my opinions, but encouraging designs that suggest to developers their code can recover or rollback is a bad idea. It's bad because I don't really think you can rollback the corruption because this was likely caused by an earlier thread doing updates not the thread that encounters an exception because of those updates and it's bad because it increases the prospect of a false sense of security. What if this lock was nested? I mean what if you're thread gets a lock in method |
Beta Was this translation helpful? Give feedback.
-
@yaakov-h And nesting. We care about this, so we removed extra nesting in @Korporal Exception in Inner will be caught by Inner |
Beta Was this translation helpful? Give feedback.
-
@rev3r - So the code in Locking code is often highly non intuitive and requires a great deal of care and attention, even with very strict, simple policies we can get almost unfathomable bugs, the "rolling back" idea makes this worse. I read Lippert's article and I don't see him actually advocating any rollback. |
Beta Was this translation helpful? Give feedback.
-
@Korporal We could rethrow if we want. |
Beta Was this translation helpful? Give feedback.
-
@rev3r - But that simply won't do, how does the called method know it should rethrow? Should it simply rethrow or throw a new kind of LockRollbackException? Should Outer then only itself rollback if it sees that exception? Very quickly this gets unfathomable IMHO and rolling back updates made with a lock held is fundamentally bad design. It cannot help or improve system quality and I've yet to see an example that makes me reconsider this. |
Beta Was this translation helpful? Give feedback.
-
@Korporal The same questions are asked when handling normal exceptions. I meant normal rethrow, not some special one. In response to blog and rollback:
He enumerates 3 solutions, each with issues. |
Beta Was this translation helpful? Give feedback.
-
I need to see an example, even Mr. Lippert didn't include one. |
Beta Was this translation helpful? Give feedback.
-
In SQL/CLR user's managed code cannot take locks, code must be static. There's reason this is disallowed. |
Beta Was this translation helpful? Give feedback.
-
@Korporal I can't provide real world examples, because I avoid |
Beta Was this translation helpful? Give feedback.
-
@rev3r - So how would you handle rollback if you were using spinlocks/InterlockCompreExchange? Can you think of an example with that where rollback helps? |
Beta Was this translation helpful? Give feedback.
-
We want make code cleaner for very common scenarios. Extremely niche scenarios taht are already served by existing constructs don't need time spent making them just a tiny bit nicer. |
Beta Was this translation helpful? Give feedback.
-
@rev3r - The reason I've argued as I have so far is that I want to understand the scenarios that can cause a code lock block to encounter an exception. If we cannot do this then fine, but if we can we should. These are the scenarios that spring to mind, there are probably more:
In the first four cases rollback is not possible, in case 1. it is not really necessary, 2. Is caused by some other thread, 3. is caused by abuse of the lock concept itself and 4 is either a system bug, OS bug or hardware problem. This leaves 5. Now in case 5. The exception is not the result of actions performed after our thread acquired the lock but by actions performed by a thread before we acquired the lock. Since our thread did not perform those actions it cannot undo them. |
Beta Was this translation helpful? Give feedback.
-
@Korporal I can't provide good example either, where it's critical, but I'm not that experienced developer myself. I just think there as some scenarios but I could be wrong and this feature would be never used in practice. Either way thanks for constructive discussion 👍 I'll leave this open, maybe someone smarter could come up with some good examples. |
Beta Was this translation helpful? Give feedback.
-
@rev3r - You're very welcome and do not think I was trying to press you or anything, I have worked on systems that rely on locks several times and the subject is very interesting and so easy to mess up, I've certainly messed things up several times myself! |
Beta Was this translation helpful? Give feedback.
-
Rolling back the state is often a complicated proposition. That's what transactional memory tries to solve. But it also ends up having the same problem that @Korporal points out. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Background
The
lock
block can lead to inconsistent state, as Eric Lippert wrote:Similar opinion was also expressed by C# async concept designer Jeffrey Richter.
Proposal
I suggest adding optional
catch
block tolock
block. That will allow developers to handle exceptions and roll back data if necessary.Alternative
Now developers needs to add yet another
try
block (becauselock
is internally atry-finally
block) to handle those situations:That is less than ideal and looks ugly.
Beta Was this translation helpful? Give feedback.
All reactions