You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@commons.apache.org by "Pavel Kolesov (JIRA)" <ji...@apache.org> on 2018/04/30 08:33:00 UTC

[jira] [Comment Edited] (POOL-340) borrowObject is stuck, if create fails

    [ https://issues.apache.org/jira/browse/POOL-340?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16458398#comment-16458398 ] 

Pavel Kolesov edited comment on POOL-340 at 4/30/18 8:32 AM:
-------------------------------------------------------------

[~garydgregory], thank you for your response and sorry for the delay. I've come up with such test (not the shortest one, unfortunately):
{code}

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

public class GenericObjectPoolTest {
    
    @Test(timeout = 10_000)
    @Ignore("Doesn't pass, waiting for https://issues.apache.org/jira/browse/POOL-340")
    public void testBorrowObjectStuck() {
        SingleObjectFactory factory = new SingleObjectFactory();
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxIdle(1);
        config.setMaxTotal(1);
        config.setBlockWhenExhausted(true);
        config.setMinIdle(0);
        config.setTestOnBorrow(true);
        config.setTestOnReturn(true);
        config.setTestWhileIdle(false);
        config.setTimeBetweenEvictionRunsMillis(-1);
        config.setMinEvictableIdleTimeMillis(-1);
        config.setSoftMinEvictableIdleTimeMillis(-1);
        
        config.setMaxWaitMillis(-1);
        GenericObjectPool<Object> pool = new GenericObjectPool<>(factory, config);
        
        AtomicBoolean failed = new AtomicBoolean();
        CountDownLatch barrier = new CountDownLatch(1);
        Thread thread1 = new Thread(new WinnerRunnable(pool, barrier, failed));
        thread1.start();
        
        // wait for object to be created
        while(!factory.created.get()) {
            sleepIgnoreException(5);
        }
        
        // now borrow
        barrier.countDown();
        try {
            pool.borrowObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        Assert.assertFalse(failed.get());
        
    }
    
    private static class SingleObjectFactory extends BasePooledObjectFactory<Object> {
        private final AtomicBoolean created = new AtomicBoolean();
        private final AtomicBoolean validated = new AtomicBoolean();
        @Override
        public Object create() throws Exception {
            if (!created.getAndSet(true)) {
                return new Object();
            }
            throw new Exception("Already created");
        }

        @Override
        public PooledObject<Object> wrap(Object obj) {
            return new DefaultPooledObject<>(new Object());
        }

        @Override
        public boolean validateObject(PooledObject<Object> p) {
            // return valid once
            if (!validated.getAndSet(true)) {
                return true;
            }
            System.out.println("invalid");
            return false;
        }
    }
    
    private static class WinnerRunnable implements Runnable {
        private final GenericObjectPool<Object> pool;
        private final AtomicBoolean failed;
        private final CountDownLatch barrier;
        private WinnerRunnable(GenericObjectPool<Object> pool, CountDownLatch barrier, AtomicBoolean failed) {
            this.pool = pool;
            this.failed = failed;
            this.barrier = barrier;
        }
        @Override
        public void run() {
            try {
                Object obj = pool.borrowObject();
                
                // wait for another thread to start borrowObject
                if (!barrier.await(5, TimeUnit.SECONDS)) {
                    System.out.println("Timeout waiting");
                    failed.set(true);
                } else {
                    // just to make sure, borrowObject has started waiting on queue
                    sleepIgnoreException(1000);
                }
                
                pool.returnObject(obj);
            } catch (Exception e) {
                failed.set(true);
                e.printStackTrace();
            }
        }
    }
    
    private static void sleepIgnoreException(long millis) {
        try {
            Thread.sleep(millis);
        } catch(Throwable e) {
            // ignore
        }
    }
}
{code}


was (Author: pavelk):
[~garydgregory], thank you for your response and sorry for the delay. I've come up with such test (not the shortest one, unfortunately):
{code}
package org.apache.commons.pool2.impl;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

public class GenericObjectPoolTest {
    
    @Test(timeout = 10_000)
    @Ignore("Doesn't pass, waiting for https://issues.apache.org/jira/browse/POOL-340")
    public void testBorrowObjectStuck() {
        SingleObjectFactory factory = new SingleObjectFactory();
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxIdle(1);
        config.setMaxTotal(1);
        config.setBlockWhenExhausted(true);
        config.setMinIdle(0);
        config.setTestOnBorrow(true);
        config.setTestOnReturn(true);
        config.setTestWhileIdle(false);
        config.setTimeBetweenEvictionRunsMillis(-1);
        config.setMinEvictableIdleTimeMillis(-1);
        config.setSoftMinEvictableIdleTimeMillis(-1);
        
        config.setMaxWaitMillis(-1);
        GenericObjectPool<Object> pool = new GenericObjectPool<>(factory, config);
        
        AtomicBoolean failed = new AtomicBoolean();
        CountDownLatch barrier = new CountDownLatch(1);
        Thread thread1 = new Thread(new WinnerRunnable(pool, barrier, failed));
        thread1.start();
        
        // wait for object to be created
        while(!factory.created.get()) {
            sleepIgnoreException(5);
        }
        
        // now borrow
        barrier.countDown();
        try {
            pool.borrowObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        Assert.assertFalse(failed.get());
        
    }
    
    private static class SingleObjectFactory extends BasePooledObjectFactory<Object> {
        private final AtomicBoolean created = new AtomicBoolean();
        private final AtomicBoolean validated = new AtomicBoolean();
        @Override
        public Object create() throws Exception {
            if (!created.getAndSet(true)) {
                return new Object();
            }
            throw new Exception("Already created");
        }

        @Override
        public PooledObject<Object> wrap(Object obj) {
            return new DefaultPooledObject<>(new Object());
        }

        @Override
        public boolean validateObject(PooledObject<Object> p) {
            // return valid once
            if (!validated.getAndSet(true)) {
                return true;
            }
            System.out.println("invalid");
            return false;
        }
    }
    
    private static class WinnerRunnable implements Runnable {
        private final GenericObjectPool<Object> pool;
        private final AtomicBoolean failed;
        private final CountDownLatch barrier;
        private WinnerRunnable(GenericObjectPool<Object> pool, CountDownLatch barrier, AtomicBoolean failed) {
            this.pool = pool;
            this.failed = failed;
            this.barrier = barrier;
        }
        @Override
        public void run() {
            try {
                Object obj = pool.borrowObject();
                
                // wait for another thread to start borrowObject
                if (!barrier.await(5, TimeUnit.SECONDS)) {
                    System.out.println("Timeout waiting");
                    failed.set(true);
                } else {
                    // just to make sure, borrowObject has started waiting on queue
                    sleepIgnoreException(1000);
                }
                
                pool.returnObject(obj);
            } catch (Exception e) {
                failed.set(true);
                e.printStackTrace();
            }
        }
    }
    
    private static void sleepIgnoreException(long millis) {
        try {
            Thread.sleep(millis);
        } catch(Throwable e) {
            // ignore
        }
    }
}
{code}

> borrowObject is stuck, if create fails
> --------------------------------------
>
>                 Key: POOL-340
>                 URL: https://issues.apache.org/jira/browse/POOL-340
>             Project: Commons Pool
>          Issue Type: Bug
>    Affects Versions: 2.4, 2.4.1, 2.4.2, 2.4.3, 2.5.0
>            Reporter: Pavel Kolesov
>            Priority: Critical
>
> After changes in 2.4.3 there is a high chance of a scenario, in which borrowObject waits infinitely, if create fails or no one calls a create.
> {noformat}
>    java.lang.Thread.State: WAITING (parking)
>         at sun.misc.Unsafe.park(Native Method)
>         - parking to wait for  <0x0000000083cfd978> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
>         at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
>         at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
>         at org.apache.commons.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:583)
>         at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:442)
>         at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:363)
> {noformat}
> If pool is exhausted, when borrowObject tries to get idle object, it waits for new object to be created.
> If all objects are returned to pool invalid and destroyed, and it is impossible to create a new one, borrowObject will not return.
> Even if afterwards it is becomes possible to crate a new object but no one creates it, borrowObject will not return either.



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)