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

GH-3444: Add Custom TTL support for RedisLock, and JdbcLock #9053

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

EddieChoCho
Copy link
Contributor

Fixes: #3444

  • Add CustomTtlLock, and CustomTtlLockRegistry interfaces
  • Modify RedisLockRegistry to implement the interfaces.
  • Modify ddl of INT_LOCK table, LockRepository, DefaultLockRepository, and JdbcLockRegistry to implement the interfaces.
  • Fix potential concurrency issue of unlock method of JdbcLock.
  • Maintain existing test cases and add new test cases.

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

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

Thanks for looking into this!

I still think it has to be called a DistributedLock from the beginning: we may come up eventually with other API, not only TTL.

I think the LockRegistry must be modified for a generic arg: LockRegistry<L extends Lock>.
And no need in the extra CustomTtlLockRegistry abstraction.

Can you elaborate, please, why do we need a result from delete()?

Since this is a huge braking change (as it is expected according to the discussion in the issue), we are going to look into this in details in the next 6.4 version, which we are going to start this June.

@EddieChoCho
Copy link
Contributor Author

Can you elaborate, please, why do we need a result from delete()?

If the result(row count) is 0, which means that the lock is not owned by current process.

For example:
ProcessA and processB are in the same region.
ProcessA acquired the distributed lock. //{REGION: r, LOCK_KEY: lock, CLIENT_ID: processA}
But the work takes too long and the ownership of the lock has been expired.
Before processA release the lock, processB acquired the same distributed lock. //{REGION: r, LOCK_KEY: lock, CLIENT_ID: processB}
When processA try to release the distributed lock. //DELETE FROM INT_LOCK WHERE REGION='r' AND LOCK_KEY='lock' AND CLIENT_ID='processA'
The result from delete() would be 0.

In this case, an IllegalStateException should be thrown to info the process that the integrity of data protected by this lock may have been compromised.

}
catch (TransientDataAccessException | TransactionTimedOutException | TransactionSystemException e) {
// try again
}
catch (IllegalStateException e) {
throw new IllegalStateException("Lock was released in the store due to expiration. " +
Copy link
Member

Choose a reason for hiding this comment

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

OK. I see why you use boolean for delete() now.
I wonder if we can use a ConcurrentModificationException instead.
Or something from the org.springframework.dao package, like ConcurrencyFailureException

Copy link
Contributor Author

Choose a reason for hiding this comment

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

RedisLock throw IllegalStateException when it face this scenario.
Should we also modify the implementation of RedisLock?

Copy link
Member

Choose a reason for hiding this comment

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

I see. Well, that was just a suggestion if that is makes sense to rely on some specific exception instead of this common.
Might be the fact that ConcurrentModificationException does not fit in our locks scenario.
However that ConcurrencyFailureException might be OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

catch (TransientDataAccessException | TransactionTimedOutException | TransactionSystemException e) {
      // try again
}

In the current implementation, TransientDataAccessException, TransactionTimedOutException, and TransactionSystemException will be caught, and then the delete() method will be invoked again.

Since ConcurrencyFailureException extends TransientDataAccessException, I think we should not throw it for this scenario.

I will change it to throw ConcurrentModificationException instead if you think ConcurrentModificationException fits better.

Copy link
Member

Choose a reason for hiding this comment

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

I see. Thanks. Missed that.

So, yeah, this ConcurrentModificationException fits in my brain because its name means exactly what happens here for us.
But I'm not sure if this is a correct scenario where we can use it from Java perspective.
That's why I'm reaching you for other opinion, arguments and so on.
Just want to be sure that we are on the same page with a decision to move on.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see,
I agree that changing from IllegalStateException to ConcurrentModificationException can improve the readability.
But I'm also not sure if this is a correct scenario where we can use it from Java perspective.

How about we create our own exception -LockOwnershipExpiredException for Spring distributed locks?
It can be used by both RedisLock, and JdbcLock.

@artembilan
Copy link
Member

This is good stuff.
Thank you very much for looking into that!

As I expected this is really huge breaking change in the API, so I'm afraid we cannot accept it right now according to our migration policies.
We have to wait until the next major version 7.0 next year.
Of course we might be able to start it in the end of this year, when Spring Framework 6.2 release train is out this November.

Please, keep in touch meanwhile.

Thank you for understanding!

…cLock

Fixes: spring-projects#3444

* Add `CustomTtlLock`, and `CustomTtlLockRegistry` interfaces
* Modify `RedisLockRegistry` to implement the interfaces.
* Modify ddl of `INT_LOCK` table, `LockRepository`, `DefaultLockRepository`, and `JdbcLockRegistry` to implement the interfaces.
* Fix potential concurrency issue of `unlock` method of `JdbcLock`.
* Maintain existing test cases and add new test cases.
Remove `CustomTtlLockRegistry`
@EddieChoCho EddieChoCho marked this pull request as draft June 26, 2024 12:54
public void delete(String lock) {
this.defaultTransactionTemplate.executeWithoutResult(
transactionStatus -> this.template.update(this.deleteQuery, this.region, lock, this.id));
public boolean delete(String lock) {

Choose a reason for hiding this comment

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

This also presents an opportunity to check for template.update() > 1 to throw exception, this will help prevent incorrect constraint on Table.

This is possible only if table is created without constraint INT_LOCK_PK primary key (LOCK_KEY, REGION).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually outside the scope of the PR. (my bad, I should not include this change).
The method has been maintained in PR #9292.
I think business logic should not address cases of misconfiguration or incorrect settings.
If you'd like to discuss this method further, it would be great to create a new issue.

@Pavankn18
Copy link

Pavankn18 commented Oct 27, 2024

Any new on this? Spring 7 will be out soon right? Will these changes be included 7.0?

Spring framework will become GA in Nov 2024 -https://spring.io/blog/2024/10/01/from-spring-framework-6-2-to-7-0.

@artembilan
Copy link
Member

Thank you both for keeping an eye on this! So, in November we are releasing 6.4. After that we are good to start 7.0. But that is going to be released only next November 2025. So, not soon enough, but we still don’t have a choice with such a breaking change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Custom TTL per LOCK in LockRegistry
3 participants