Skip to content

Commit 5ebe9f5

Browse files
committed
Refine DistributedLock implementation and update project documentation
- Improve DistributedLock: fix reentrancy, add deadline checks in tryLock, and use id.intern() for synchronization. - Update Watcher and LockKey for better distributed lock management. - Add comprehensive JUnit 5 tests for DistributedLock covering timeouts, reentrancy, and concurrency. - Update README.md with Maven Repository badge. - Update DEVELOPER_GUIDE.md with lock usage examples. - Update .gitignore to exclude .cursor, .vscode, jdk-17, and .lock files.
1 parent 3c10c7a commit 5ebe9f5

File tree

7 files changed

+241
-193
lines changed

7 files changed

+241
-193
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
_
66
/target/
77
/lib/
8+
.cursor/
9+
.vscode/
10+
jdk-17/
11+
.lock

DEVELOPER_GUIDE.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,12 @@ request.setMethod("POST")
240240
.setBody("{\"key\":\"value\"}");
241241

242242
HTTPHandler handler = new HTTPHandler();
243-
String responseBody = handler.handleRequest(request).getBody();
243+
URLResponse response = handler.handleRequest(request);
244+
if (response.getStatusCode() == 201 || response.getStatusCode() == 200) {
245+
logger.log(Level.INFO, "API successfully");
246+
} else {
247+
logger.log(Level.WARNING, "API returned status "+ response.getStatusCode());
248+
}
244249
```
245250

246251
---

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ The tinystruct framework
77
A simple framework for Java development. Simple thinking, Better design, Easy to be used with better performance!
88

99
[![Star History Chart](https://api.star-history.com/svg?repos=tinystruct/tinystruct&type=Date)](https://www.star-history.com/#tinystruct/tinystruct&Date)
10+
[![MvnRepository](https://badges.mvnrepository.com/badge/org.tinystruct/tinystruct/badge.svg?label=MvnRepository)](https://mvnrepository.com/artifact/org.tinystruct/tinystruct)
11+
1012

1113
## Prerequisites
1214

src/main/java/org/tinystruct/valve/DistributedLock.java

Lines changed: 78 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
/**
1313
* Distributed lock implementation based on the File system.
1414
* Usage:
15+
*
1516
* <pre>
1617
* {@code
1718
* Lock lock = Watcher.getInstance().acquire();
@@ -32,11 +33,13 @@ public class DistributedLock implements Lock {
3233
private final String id;
3334
private final Watcher watcher;
3435
private volatile Thread owner;
36+
private volatile int holdCount = 0;
3537

3638
private static final Logger logger = Logger.getLogger(DistributedLock.class.getName());
3739

3840
/**
39-
* Constructor to create a new DistributedLock instance with a random UUID as the lock ID.
41+
* Constructor to create a new DistributedLock instance with a random UUID as
42+
* the lock ID.
4043
*/
4144
public DistributedLock() {
4245
this.id = UUID.randomUUID().toString();
@@ -46,7 +49,8 @@ public DistributedLock() {
4649
}
4750

4851
/**
49-
* Constructor to create a new DistributedLock instance with a specified lock ID.
52+
* Constructor to create a new DistributedLock instance with a specified lock
53+
* ID.
5054
*
5155
* @param idb Byte array representing the lock ID.
5256
*/
@@ -62,18 +66,20 @@ public DistributedLock(String lockId) {
6266
}
6367

6468
/**
65-
* Acquires the lock. If the lock is not available, it waits until the lock is released.
69+
* Acquires the lock. If the lock is not available, it waits until the lock is
70+
* released.
6671
*/
6772
@Override
6873
public void lock() {
6974
Thread current = Thread.currentThread();
7075
// Check for reentrant lock
7176
if (current == owner) {
77+
holdCount++;
7278
return;
7379
}
7480

7581
try {
76-
while (!tryLock(1000, TimeUnit.MILLISECONDS)) {
82+
while (!tryLock(1, TimeUnit.SECONDS)) {
7783
logger.log(Level.FINE, "Waiting for lock to be released...");
7884
}
7985
} catch (ApplicationException e) {
@@ -82,7 +88,8 @@ public void lock() {
8288
}
8389

8490
/**
85-
* Attempts to acquire the lock without waiting. Returns true if the lock was acquired successfully.
91+
* Attempts to acquire the lock without waiting. Returns true if the lock was
92+
* acquired successfully.
8693
*/
8794
@Override
8895
public boolean tryLock() {
@@ -99,38 +106,61 @@ public boolean tryLock() {
99106
*
100107
* @param timeout The maximum time to wait for the lock.
101108
* @param unit The time unit of the timeout parameter.
102-
* @return True if the lock was acquired successfully within the specified time, false otherwise.
109+
* @return True if the lock was acquired successfully within the specified time,
110+
* false otherwise.
103111
* @throws ApplicationException If an error occurs during lock acquisition.
104112
*/
105113
@Override
106114
public boolean tryLock(long timeout, TimeUnit unit) throws ApplicationException {
107115
Thread current = Thread.currentThread();
108-
// Check for reentrant lock
109-
if (current == owner) {
110-
return true;
111-
}
116+
long deadline = timeout > 0 ? System.nanoTime() + unit.toNanos(timeout) : 0;
117+
118+
while (true) {
119+
// Check deadline before attempting to acquire
120+
if (timeout > 0) {
121+
long remaining = deadline - System.nanoTime();
122+
if (remaining <= 0) {
123+
return false;
124+
}
125+
}
112126

113-
synchronized (this) {
114-
// If the lock is existing, then wait for it to be released.
115-
if (watcher.watch(this)) {
116-
try {
117-
if (timeout > 0) {
118-
watcher.waitFor(this.id, timeout, unit);
119-
} else {
120-
watcher.waitFor(this.id);
121-
}
122-
} catch (InterruptedException e) {
123-
Thread.currentThread().interrupt(); // Preserve interrupt status
124-
logger.severe("Error while waiting for lock: " + e.getMessage());
125-
throw new ApplicationException(e.getMessage(), e.getCause());
127+
synchronized (this.id.intern()) {
128+
// Check for reentrant lock
129+
if (current == owner) {
130+
holdCount++;
131+
return true;
132+
}
133+
134+
// If the lock is not currently held by anyone, acquire it
135+
if (!watcher.watch(this)) {
136+
this.watcher.register(this);
137+
this.owner = current;
138+
this.holdCount = 1;
139+
return true;
126140
}
127141
}
128142

129-
// Register the lock and set owner
130-
this.watcher.register(this);
131-
this.owner = current;
143+
// Lock is held by another thread - wait outside the synchronized block
144+
if (timeout <= 0) {
145+
return false;
146+
}
147+
148+
long remaining = deadline - System.nanoTime();
149+
if (remaining <= 0) {
150+
return false;
151+
}
152+
153+
try {
154+
if (!watcher.waitFor(this.id, remaining, TimeUnit.NANOSECONDS)) {
155+
return false;
156+
}
157+
} catch (InterruptedException e) {
158+
Thread.currentThread().interrupt();
159+
logger.severe("Error while waiting for lock: " + e.getMessage());
160+
throw new ApplicationException(e.getMessage(), e.getCause());
161+
}
162+
// Loop back to try acquiring the lock again
132163
}
133-
return true;
134164
}
135165

136166
/**
@@ -139,20 +169,24 @@ public boolean tryLock(long timeout, TimeUnit unit) throws ApplicationException
139169
@Override
140170
public void unlock() {
141171
Thread current = Thread.currentThread();
142-
if (owner == null) return;
143-
if (current != owner) {
144-
throw new IllegalMonitorStateException(
145-
"Thread " + current.getName() +
146-
" attempting to unlock while not holding the lock"
147-
);
148-
}
172+
synchronized (this.id.intern()) {
173+
if (owner == null)
174+
return;
175+
if (current != owner) {
176+
throw new IllegalMonitorStateException(
177+
"Thread " + current.getName() +
178+
" attempting to unlock while not holding the lock");
179+
}
149180

150-
try {
151-
watcher.unregister(this);
152-
owner = null;
153-
} catch (ApplicationException e) {
154-
logger.severe("Error while unlocking: " + e);
155-
throw new ApplicationRuntimeException(e.getMessage(), e.getCause());
181+
if (--holdCount == 0) {
182+
try {
183+
watcher.unregister(this);
184+
owner = null;
185+
} catch (ApplicationException e) {
186+
logger.severe("Error while unlocking: " + e);
187+
throw new ApplicationRuntimeException(e.getMessage(), e.getCause());
188+
}
189+
}
156190
}
157191
}
158192

@@ -174,8 +208,10 @@ public String id() {
174208
*/
175209
@Override
176210
public boolean equals(Object obj) {
177-
if (this == obj) return true;
178-
if (obj == null || getClass() != obj.getClass()) return false;
211+
if (this == obj)
212+
return true;
213+
if (obj == null || getClass() != obj.getClass())
214+
return false;
179215
DistributedLock other = (DistributedLock) obj;
180216
return Objects.equals(id, other.id);
181217
}

src/main/java/org/tinystruct/valve/LockKey.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public String value() {
2727
fixedBytes[i] = idb[i];
2828
else {
2929
try {
30-
fixedBytes[i] = (byte) (alphabetNumbers[SecureRandom.getInstance("SHA1PRNG").nextInt(alphabetNumbers.length - 1)]);
30+
fixedBytes[i] = (byte) (alphabetNumbers[SecureRandom.getInstance("SHA1PRNG")
31+
.nextInt(alphabetNumbers.length - 1)]);
3132
} catch (NoSuchAlgorithmException e) {
3233
throw new ApplicationRuntimeException(e);
3334
}

0 commit comments

Comments
 (0)