Repository: redis/redis-py
Pull request: fix(lock): Fix LockError message when releasing a lock.
Fix #3535 

### Pull Request check-list

_Please make sure to review and check all of these items:_

- [ ] Do tests and lints pass with this change?
- [ ] Do the CI tests pass with this change (enable it first in your forked repo and wait for the github action build to finish)?
- [ ] Is the new or changed code fully tested?
- [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?
- [ ] Is there an example added to the examples folder (if applicable)?
- [ ] Was the change added to CHANGES file?

_NOTE: these things are not required to open a PR and can be done
afterwards / while the PR is open._

### Description of change

We can get expect result for the test code in #3535  with this implementation:
```
Thread 2: Lock error: Cannot release a lock that's no longer owned
```

Problem statement:
Release a lock from a non-owned thread should raise proper error
Reproduce code:

```python
import redis
import threading
import time


# Create Redis client
r = redis.Redis()
lock_name = "lock:example"


def thread1_function():
    print("Thread 1: Starting")
    
    lock = r.lock(lock_name, timeout=5)
    if lock.acquire():
        print("Thread 1: Lock acquired")


def thread2_function():
    print("Thread 2: Starting")
    lock = r.lock(lock_name)
    try:
        lock.release()
    except Exception as e:
        print(f"Thread 2: Lock error: {e}")


# Create and start threads
t1 = threading.Thread(target=thread1_function)
t2 = threading.Thread(target=thread2_function)

t1.start()
time.sleep(1)
t2.start()

# Wait for threads to complete
t1.join()
t2.join()

# clean up
r.delete(lock_name)

```

Output:
```
Thread 1: Starting
Thread 1: Lock acquired
Thread 2: Starting
Thread 2: Lock error: Cannot release an unlocked lock
```

Currently, if we release lock from thread2, we got Lock error with `Lock error: Cannot release an unlocked lock`, which is not true actually and will mislead the user in a way.

Hints from issue discussion:

Commit message: fix(lock): raise LockNotOwnedError when release a lock from non-owned thread

Patch to review:
diff --git a/redis/lock.py b/redis/lock.py
index f44ed629da..7d77bafb95 100644
--- a/redis/lock.py
+++ b/redis/lock.py
@@ -251,7 +251,13 @@ def release(self) -> None:
         """
         expected_token = self.local.token
         if expected_token is None:
-            raise LockError("Cannot release an unlocked lock", lock_name=self.name)
+            if self.owned():
+                raise LockError("Cannot release an unlocked lock", lock_name=self.name)
+            else:
+                raise LockNotOwnedError(
+                    "Cannot release a lock that's no longer owned",
+                    lock_name=self.name,
+                )
         self.local.token = None
         self.do_release(expected_token)
 


Code review comments:
diff_hunk: @@ -251,7 +251,13 @@ def release(self) -> None:
         """
         expected_token = self.local.token
         if expected_token is None:
-            raise LockError("Cannot release an unlocked lock", lock_name=self.name)
+            if self.owned():
path: redis/lock.py
text: @user1:
This will always return 'False' here. On the previous line we already have a check if self.local.token is None and owned() returns False at the moment it detects None for it.

@author:
It does, thanks!