Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Lock.lockInterruptibly only where it may actually be needed #1220

Merged
merged 3 commits into from
Oct 6, 2023

Conversation

stIncMale
Copy link
Member

This PR reverts most of the behavioral changes done in #1206.

JAVA-5189

@stIncMale stIncMale self-assigned this Oct 6, 2023
@stIncMale stIncMale requested review from jyemin and rozza October 6, 2023 04:35
@rozza rozza mentioned this pull request Oct 6, 2023
Copy link
Member

@rozza rozza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one naming nit - other than that LGTM.

I ran the slow tests and the build is happy again :)

@@ -28,6 +28,26 @@
* <p>This class is not part of the public API and may be removed or changed at any time</p>
*/
public final class Locks {
public static void withUninterruptibleLock(final Lock lock, final Runnable action) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming nits: Just for clarity and to mirror the lock method names, I recommend:

withLock - should use lock.lock()
withLockInterruptibly - should use lock.lockInterruptibly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this, both for consistency with the Lock interface, and also because the negative "un" increases my cognitive load when reasoning about it. Also, I think for locking what we're saying is that we should lock uninterruptibly "by default", and only lock interruptibly when when we have a reason. So having a simple name for the default case is a good thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also agree with this naming, but I had to make the names the way they are because the original methods that were in Locks (withLock, checkedWithLock) use interruptible locking. So to use the naming scheme proposed by Ross (it's also consistent with the naming in the Lock API), I will have to rename the original Locks.withLock/checkedWithLock methods, which will affect the code that have not yet been touched either by #1206 or by the current PR. If this is OK, I will be glad to do the renaming.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's ok. Proceed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The rename was automatic, followed by semi-manual "import the method statically" in places where it was possible (IntelliJ does not preserve static imports when refactoring).

Copy link
Contributor

@jyemin jyemin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have a mix of interruptible and "normal" locking, when I look at all the places where they are used, I'm not always sure which is right. For example, I'm not clear why ClusterClock uses interruptible locking. Is there a rule of thumb that we can state?

@@ -28,6 +28,26 @@
* <p>This class is not part of the public API and may be removed or changed at any time</p>
*/
public final class Locks {
public static void withUninterruptibleLock(final Lock lock, final Runnable action) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this, both for consistency with the Lock interface, and also because the negative "un" increases my cognitive load when reasoning about it. Also, I think for locking what we're saying is that we should lock uninterruptibly "by default", and only lock interruptibly when when we have a reason. So having a simple name for the default case is a good thing.

@stIncMale
Copy link
Member Author

I ran the slow tests and the build is happy again :)

Yeah, I also included them in the patch created by GitHub for this PR. Additionally, here are the results of running everything: https://spruce.mongodb.com/version/651f7a3d2a60ed945c2fdff4/tasks?page=0&sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC (the only failures are of the AggregatesTest, which is fixed by #1218.

@stIncMale
Copy link
Member Author

stIncMale commented Oct 6, 2023

Now that we have a mix of interruptible and "normal" locking, when I look at all the places where they are used, I'm not always sure which is right. For example, I'm not clear why ClusterClock uses interruptible locking. Is there a rule of thumb that we can state?

We had this mix before doing changes in #1206 and the current PR, i.e., we are not in a new situation.

I'm not clear why ClusterClock uses interruptible locking

Neither am I, it does not need to use interruptible locking, if we talk about using interruptible locking conservatively. But it has already been like this, and things were working, so I did not change it.

Is there a rule of thumb that we can state?

Here are my thoughts at the moment, but take them with a grain of salt (also, they hardly can be expressed as an algorithm, at best they can be rules of thumb, as you said):

Interruptible locking is likely not needed:

  1. If code in a critical section is either non-blocking, or the code is blocking, but while blocking it releases the lock guarding the critical section (for example, Condition.await). Sometimes we still have to "violate" this rule, like in a few places in the connection pool, just because there is at least one pre-existing test that fails if locking is not interruptible.
  2. If code in a critical must be executed no matter what (unless the process is about to be terminated). For example, interruptible locking is likely harmful if a critical section completes a callback, or if it is the body of a finally block.

Update: Item 2 is what put us in trouble, and is why I had to start using interruptible locking conservatively. Despite interruptible locking not being harmful in situations described by item 1, it is not always that obvious whether the code cannot be also part of a situation from item 2 (one would have to check all the current usages and think about possible future uses). Consequently, I crawled into the "don't use interruptible locking unless it's actually needed, even if it appears to be acceptable and not harmful" corner out of caution/fear.

@jyemin
Copy link
Contributor

jyemin commented Oct 6, 2023

I'm not clear why ClusterClock uses interruptible locking

Neither am I, it does not need to use interruptible locking, if we talk about using interruptible locking conservatively. But it has already been like this, and things were working, so I did not change it.

I did it in scope of JAVA-4642. I don't think it does any harm, but it does seem unnecessary.

@stIncMale stIncMale requested a review from rozza October 6, 2023 18:39
Copy link
Contributor

@jyemin jyemin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@stIncMale
Copy link
Member Author

Will merge, because @rozza LGTMed with the condition that the methods are renamed, and they were renamed.

@stIncMale stIncMale merged commit e1b4b90 into mongodb:master Oct 6, 2023
50 of 57 checks passed
@stIncMale stIncMale deleted the JAVA-5189 branch October 6, 2023 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants