Skip to content

Commit df45c09

Browse files
exceptions handling for create object is thrown directly, and minor additions to pool stats methods (#4)
1 parent 3362fdd commit df45c09

File tree

5 files changed

+88
-28
lines changed

5 files changed

+88
-28
lines changed

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
<parent>
88
<groupId>today.bonfire.oss</groupId>
99
<artifactId>bonfire-oss-parent</artifactId>
10-
<version>1.1.4</version>
10+
<version>1.1.5</version>
1111
</parent>
1212

1313
<artifactId>simple-object-pool</artifactId>
14-
<version>2.1.0</version>
14+
<version>2.2.0</version>
1515
<packaging>jar</packaging>
1616

1717
<properties>

src/main/java/today/bonfire/oss/sop/PooledObject.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class PooledObject<T extends PoolObject> {
1818
private long timesBorrowed;
1919

2020
/**
21-
* Creates a new PooledEntity wrapping the given object with a specified ID.
21+
* Creates a new PooledObject wrapping the given object with a specified ID.
2222
* Initializes the object in an idle, non-broken state with current timestamp.
2323
*
2424
* @param object The object to be pooled

src/main/java/today/bonfire/oss/sop/SimpleObjectPool.java

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class SimpleObjectPool<T extends PoolObject> implements AutoCloseable {
4141
private final Condition retryCreationWait;
4242
private final AtomicLong objectCreateCount = new AtomicLong(0);
4343
private final AtomicInteger currentPoolSize = new AtomicInteger(0);
44+
private final AtomicLong timesBorrowed = new AtomicLong(0);
4445

4546
public SimpleObjectPool(SimpleObjectPoolConfig config, PooledObjectFactory<T> factory) {
4647
this.config = config;
@@ -172,7 +173,7 @@ private void evictionRun() {
172173

173174
if (!objectsToDestroy.isEmpty()) {
174175
log.debug("Evicted {} idle objects. Current pool size: {}, Idle: {}, Borrowed: {}",
175-
objectsToDestroy.size(), currentPoolSize(), idleObjectCount(), borrowedObjectsCount());
176+
objectsToDestroy.size(), currentPoolSize(), idleObjectCount(), borrowedObjectsCount());
176177
objectsToDestroy.clear();
177178
}
178179
}
@@ -256,7 +257,8 @@ private PooledObject<T> createObject() {
256257
try {
257258
pooledObject = new PooledObject<>(factory.createObject(), objectCreateCount.incrementAndGet());
258259
} catch (Exception e) {
259-
throw new PoolObjectException("Failed to create object", e);
260+
log.error("Failed to create object for the pool: {}", e.getMessage());
261+
throw e;
260262
}
261263
if (config.testOnCreate()) {
262264
if (!factory.isObjectValid(pooledObject.object())) {
@@ -319,9 +321,15 @@ public T borrowObject() throws PoolException {
319321
pooledObject = createObject();
320322
createdObject = true;
321323
retriesLeft--;
322-
} catch (PoolObjectException | PoolObjectValidationException e) {
323-
log.error("Failed to create objects when borrowing", e);
324+
} catch (Exception e) {
325+
if (e instanceof PoolObjectValidationException) {
326+
log.error("", e);
327+
}
324328
retriesLeft--;
329+
if (retriesLeft < 0) {
330+
// we have exhausted retries and throw the actual exception so that the caller can handle it
331+
throw e;
332+
}
325333
continue;
326334
}
327335
}
@@ -343,11 +351,13 @@ public T borrowObject() throws PoolException {
343351
continue;
344352
}
345353

354+
346355
// Object is valid, prepare for borrowing
347356
pooledObject.borrow();
348357
factory.activateObject(object);
349358
borrowedObjects.put(pooledObject.id(), pooledObject);
350359
if (createdObject) currentPoolSize.incrementAndGet();
360+
timesBorrowed.incrementAndGet();
351361
notEmpty.signal();
352362
log.trace("Resource borrowed - id: {}, current pool size: {}",
353363
pooledObject.id(), currentPoolSize.get());
@@ -380,43 +390,43 @@ public void returnObject(T obj, boolean broken) throws PoolObjectException {
380390

381391
try {
382392
lock.lock();
383-
var pooledEntity = borrowedObjects.get(obj.getEntityId());
384-
if (pooledEntity == null) {
393+
var pooledObject = borrowedObjects.get(obj.getEntityId());
394+
if (pooledObject == null) {
385395
log.warn("Attempted returning object that is not in borrowed objects list. id: {}", obj.getEntityId());
386396
return;
387397
}
388398
if (broken) {
389-
pooledEntity.broken(true);
399+
pooledObject.broken(true);
390400
}
391401
// First check if object is broken
392-
boolean isValid = !pooledEntity.isBroken();
402+
boolean isValid = !pooledObject.isBroken();
393403

394404
// Then perform testOnReturn validation if configured and object isn't already invalid
395405
if (isValid && config.testOnReturn()) {
396406
try {
397407
isValid = factory.isObjectValid(obj);
398408
} catch (Exception e) {
399409
log.error("Error validating object on return", e);
410+
// exception is swallowed since object is already invalid and will be destroyed.
400411
isValid = false;
401412
}
402413
}
403414

404415
if (!isValid) {
405-
removeAndDestroyBorrowedObjects(pooledEntity);
406-
log.warn("Returned broken or invalid entity with id {} and destroyed it.", pooledEntity.id());
416+
log.warn("Returned broken or invalid entity with id {} and destroying it.", pooledObject.id());
417+
removeAndDestroyBorrowedObjects(pooledObject);
407418
} else {
408419

409420
factory.passivateObject(obj);
410-
borrowedObjects.remove(pooledEntity.id());
411-
pooledEntity.markIdle();
412-
idleObjects.add(pooledEntity);
421+
borrowedObjects.remove(pooledObject.id());
422+
pooledObject.markIdle();
423+
idleObjects.add(pooledObject);
413424
log.trace("Object returned - id: {}, current pool size: {}",
414-
pooledEntity.id(), currentPoolSize.get());
425+
pooledObject.id(), currentPoolSize.get());
415426
notEmpty.signal();
416427
}
417428
} catch (Exception e) {
418-
log.error("Error returning object to pool", e);
419-
throw new PoolObjectException("Failed to return object to pool", e);
429+
throw new PoolObjectException("Unable to properly return object back to pool", e);
420430
} finally {
421431
lock.unlock();
422432
}
@@ -530,4 +540,43 @@ public long numOfObjectsCreated() {
530540
public int waitingCount() {
531541
return lock.getQueueLength();
532542
}
543+
544+
545+
/**
546+
* Returns the total number of times objects have been borrowed from this pool
547+
* since its creation.
548+
*
549+
* @return the total number of times objects have been borrowed
550+
*/
551+
public long numOfTimesBorrowedFromPool() {
552+
return timesBorrowed.get();
553+
}
554+
555+
556+
/**
557+
* Returns the number of times a specific object has been borrowed from the pool
558+
* since its creation.
559+
* <p>
560+
* Note that this method will only return statistics for objects that are currently
561+
* borrowed from the pool. For objects that are idle this will have to iterate and will have a delay.
562+
* If you need to check, check only for borrowed objects.
563+
*
564+
* @param objectId the id of the object to query, may be null
565+
* @return the number of times the object has been borrowed
566+
*/
567+
public long numOfTimesBorrowed(Long objectId) {
568+
if (objectId == null) return 0;
569+
var pooledObject = borrowedObjects.get(objectId);
570+
if (pooledObject == null) {
571+
log.warn("Object with id {} not found in borrowed objects", objectId);
572+
pooledObject = idleObjects.stream()
573+
.filter(pooledObject1 -> pooledObject1.id().equals(objectId))
574+
.findAny().orElse(null);
575+
}
576+
if (pooledObject == null) {
577+
log.warn("Object with id {} not found in idle objects", objectId);
578+
return 0;
579+
}
580+
return pooledObject.timesBorrowed();
581+
}
533582
}

src/test/java/today/bonfire/oss/sop/SimpleObjectPoolConfigTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import org.junit.jupiter.api.AfterEach;
66
import org.junit.jupiter.api.BeforeEach;
77
import org.junit.jupiter.api.Test;
8-
import org.slf4j.Logger;
98
import org.slf4j.LoggerFactory;
109

1110
import java.time.Duration;
@@ -16,7 +15,7 @@
1615

1716
class SimpleObjectPoolConfigTest {
1817

19-
private static final Logger log = LoggerFactory.getLogger(SimpleObjectPoolConfig.class);
18+
private static final org.slf4j.Logger log = LoggerFactory.getLogger(SimpleObjectPoolConfig.class);
2019
private ListAppender<ILoggingEvent> listAppender;
2120

2221
@BeforeEach

src/test/java/today/bonfire/oss/sop/SimpleObjectPoolPerformanceTest.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package today.bonfire.oss.sop;
22

3+
import lombok.extern.slf4j.Slf4j;
34
import org.junit.jupiter.api.Test;
45
import org.junit.jupiter.api.extension.ExtendWith;
56
import org.mockito.junit.jupiter.MockitoExtension;
@@ -15,14 +16,14 @@
1516

1617
import static org.assertj.core.api.Assertions.assertThat;
1718

18-
@ExtendWith(MockitoExtension.class)
19+
@Slf4j @ExtendWith(MockitoExtension.class)
1920
class SimpleObjectPoolPerformanceTest {
2021

2122
private static final int MAX_POOL_SIZE = 10;
2223
private static final int MIN_POOL_SIZE = 5;
23-
private static final long IDLE_TIMEOUT = 1000L;
24-
private static final long ABANDONED_TIMEOUT = 2000L;
25-
private static final int NUM_BORROW_REQUESTS = 20000;
24+
private static final long IDLE_TIMEOUT = 10000L;
25+
private static final long ABANDONED_TIMEOUT = 20000L;
26+
private static final int NUM_BORROW_REQUESTS = 10000;
2627

2728

2829
@Test
@@ -31,7 +32,8 @@ void testHighConcurrencyBorrowAndReturn() throws Exception {
3132
var pool = new SimpleObjectPool<>(SimpleObjectPoolConfig.builder()
3233
.maxPoolSize(MAX_POOL_SIZE)
3334
.minPoolSize(MIN_POOL_SIZE)
34-
.testWhileIdle(true)
35+
.testWhileIdle(false)
36+
.waitingForObjectTimeout(Duration.ofSeconds(20))
3537
.objEvictionTimeout(Duration.ofMillis(IDLE_TIMEOUT))
3638
.abandonedTimeout(Duration.ofMillis(ABANDONED_TIMEOUT))
3739
.build(), factory);
@@ -45,16 +47,26 @@ void testHighConcurrencyBorrowAndReturn() throws Exception {
4547
try {
4648
var entity = pool.borrowObject();
4749
assertThat(entity).isNotNull();
50+
Thread.sleep(10);
4851
borrowCounter.incrementAndGet();
52+
// log.debug("Borrowed object with id {}", borrowCounter.incrementAndGet());
4953
pool.returnObject(entity);
5054
} catch (Exception e) {
51-
throw new RuntimeException("Unexpected exception: " + e.getMessage());
55+
throw new RuntimeException(e);
5256
}
5357
}, executor));
5458
}
5559
assertThat(factory.getIdCounter().get()).isEqualTo(MAX_POOL_SIZE);
56-
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS);
60+
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(30, TimeUnit.SECONDS);
61+
assertThat(pool.numOfTimesBorrowedFromPool()).isEqualTo(NUM_BORROW_REQUESTS);
62+
log.info("pool size: {}, times borrowed: {}", pool.currentPoolSize(), pool.numOfTimesBorrowedFromPool());
63+
for (var i = 0; i < MAX_POOL_SIZE; i++) {
64+
var entity = pool.borrowObject();
65+
final var timesBorrowed = pool.numOfTimesBorrowed(entity.getEntityId());
66+
log.debug("Borrowed object's count {} with id {}", timesBorrowed, entity.getEntityId());
67+
}
5768
assertThat(borrowCounter.get()).isEqualTo(NUM_BORROW_REQUESTS);
69+
pool.close();
5870
executor.shutdown();
5971
assertThat(executor.awaitTermination(5, TimeUnit.SECONDS)).isTrue();
6072
}

0 commit comments

Comments
 (0)