3

I'm struggling to figure out how to pool resources and I'm starting to suspect my threads maybe the problem(not 100% but been experimenting with it). The gist of what I'm trying to do is create a pool of channels to a server and then see if threads are using them. I've been successful at getting the number of channels to be created for as many items I'm uploading(i.e. its not pooling and just creating new channels in each thread) and successful at creating only one channel(i.e. not pooling or creating new channels as needed).

I was thinking maybe the way the threads are interacting with the pool is the problem so I tried to create newCachedThreadPool so the threads don't die as long as there's work but when I do I get errors saying the channel being used as been closed. There is a destroyObject method in my pool but I never call it so I don't understand why its being triggered(if I comment it out then it works but only creates one channel and upload is very very slow about 300 operations/second versus without threaded pool I get 30k/second). I suspect its terminating, is there any way to verify this and is there an alterative I can use if it is terminating?

Here's the code(ignore all the rabbitmq stuff, its just so I can monitor the results):

import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;

import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;

public class PoolExample {

    private static ExecutorService executor_worker;

    static {
        final int numberOfThreads_ThreadPoolExecutor = 20;
        executor_worker = Executors.newCachedThreadPool();
        executor_worker = new ThreadPoolExecutor(numberOfThreads_ThreadPoolExecutor, numberOfThreads_ThreadPoolExecutor, 1000, TimeUnit.SECONDS,
                                           new LinkedBlockingDeque<Runnable>());
    }

    private static ObjectPool<Channel> pool;

    public static void main(String[] args) throws Exception {
        System.out.println("starting..");           
        ObjectPool<Channel> pool =
                new GenericObjectPool<Channel>(
                new ConnectionPoolableObjectFactory(), 50);
        for (int x = 0; x<500000000; x++) {
            executor_worker.submit(new MyRunnable(x, pool));
        }
        //executor_worker.shutdown();
        //pool.close();
    }
}

 class ConnectionPoolableObjectFactory extends BasePoolableObjectFactory<Channel> {
     Channel channel;
     Connection connection;

    public ConnectionPoolableObjectFactory() throws IOException {
        System.out.println("hello world");
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        channel = connection.createChannel(); 
    }

    @Override
    public Channel makeObject() throws Exception {  
        //channel = connection.createChannel(); 
        return channel; 
    }

    @Override
    public boolean validateObject(Channel channel) {
        return channel.isOpen();
    }

    @Override
    public void destroyObject(Channel channel) throws Exception {
        channel.close();
    }

    @Override
    public void passivateObject(Channel channel) throws Exception {
        //System.out.println("sent back to queue");
    }
}

class MyRunnable implements Runnable{  
    protected int x = 0;
    protected ObjectPool<Channel> pool;

    public MyRunnable(int x, ObjectPool<Channel> pool) {
        // TODO Auto-generated constructor stub
        this.x = x;
        this.pool = pool;
    }

    public void run(){
        try {
                Channel channel = pool.borrowObject();
                String message = Integer.toString(x);
                channel.basicPublish( "", "task_queue", 
                        MessageProperties.PERSISTENT_TEXT_PLAIN,
                        message.getBytes());
                pool.returnObject(channel);
        } catch (NoSuchElementException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalStateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
    }
}

p.s. I basically asked a few questions and read the documentation and tried to figure this out and along the way I may have gone totally in the wrong direction so if there's any problems you see or tips then please send them my way.

The plot thickens:

In the main method's for loop(where I submit the work to the threads) I added:

    Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
    System.out.println(threadSet.size()); //number of threads
    System.out.println(pool.getNumActive());

It shows me 25 threads(although I said 20) and 20 items in the pool. But when I look at the rabbitmq UI, I see one connection with only one channel. If I create channels and submit to the runnable then it creates many channels(but it never closes them). I don't understand what's going on and why the result is not as expected.

Lostsoul
  • 25,013
  • 48
  • 144
  • 239
  • Probably unrelated but: is it on purpose that you assign 2 executors to `executor_worker` (the line `executor_worker = Executors.newCachedThreadPool();` does not do anything in your code)? – assylias May 02 '12 at 16:32
  • @assylias I read a tutorial and in their example that was how they implemented newCachedThreadPool. I didn't realize it was incorrect, I thought that was how to tell the executor to use a cachedthreadpool – Lostsoul May 02 '12 at 16:35
  • If you want to use a standard cached thread pool, just write: `executor_worker = Executors.newCachedThreadPool(numberOfThreads_ThreadPoolExecutor);` and remove the other line. – assylias May 02 '12 at 16:36
  • It didn't work, so I changed it to newFixedThreadPool instead to test but am still getting the same problem. It seems to be killing the pool upon each work the thread does. – Lostsoul May 02 '12 at 16:40

1 Answers1

1

I think the problem is that your ConnectionPoolableObjectFactory is only ever creating a single Channel object. It seems that it should be creating a new Channel everytime makeObject is called.

So maybe it should be implemented something like this:

public class ConnectionPoolableObjectFactory
        extends BasePoolableObjectFactory<Channel> {

    private final Connection connection;

    private ConnectionPoolableObjectFactory() {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        connection = factory.newConnection();
    }

    @Override
    public Channel makeObject() throws Exception {
        return connection.createChannel();
    }

    @Override
    public boolean validateObject(Channel channel) {
        return channel.isOpen();
    }

    @Override
    public void destroyObject(Channel channel) throws Exception {
        channel.close();
    }

    @Override
    public void passivateObject(Channel channel) throws Exception {
        //System.out.println("sent back to queue");
    }
}

This assumes each factory creates multiple channels from a single connection.

Jeremy
  • 22,188
  • 4
  • 68
  • 81
  • Interesting..Your code creates a connection per thread and then one channel per connection. Its definitely a step closer for me but I was trying to maintain several channels spawn from only one connection. I tried to change your code method `newConnectionFactory` to return a newconnection to a variable but then I get a null pointer exception. Any idea how to create the connection only once? – Lostsoul May 02 '12 at 19:23
  • Where is the NullPointerException coming from? I modified my answer so that new channels are created from a single connection. I can't run it because I don't have time to setup a complete program. – Jeremy May 02 '12 at 19:56
  • Thank you so much Jeremy. It worked! The nullpointer was coming from the area I borrow the channel(which basically meant it wasn't getting a channel). Your fix worked, I now have one connection with several channels. – Lostsoul May 02 '12 at 20:14
  • No problem! I updated my code so that the Connection instance was not static. It was an oversight on my part. If it were static, then each ConnectionPoolableObjectFactory instance would be sharing the same connection, which I don't think is desirable. – Jeremy May 02 '12 at 20:51
  • Thanks so much Jeremy. I actually want all instances to share a common connection but to get unique channels. Both your codes seem to work though but both are blocking like crazy. I've spent most of today trying to understand why. When I check the rabbit ui, I see 1 connection and 20 channels which is what I want but when I profile and check threads then I see them all red except one at a time. outputing stats on the pool shows there are 20 channels but I can't figure out why its doing this. Sorry if I'm bugging you too much but do you have any ideas on how I can troubleshoot this? – Lostsoul May 04 '12 at 00:16
  • when I create a similar script in which one connection creates several treads in a threaded for loop then there is zero blocking but I'm not really pooling because I'm creating new channels each time. I don't think this has anything to do with rabbit(I did email their support) I think its something to do with how the objects are being shared. Its so strange, it'll start then slow down to zero and not move. – Lostsoul May 04 '12 at 00:17
  • Does a red channel mean it is blocking? I suppose it's possible that channels are not being returned to the pool, given that you aren't returning them in a finally block. Also, you may need to implement "passivateObject" if there is something you need to do to prepare a channel to be used again. I played around with making a simple example [here](https://refheap.com/paste/2539). Feel free to play with it, I was just trying to reproduce your issue with out needing to use rabbitmq, but I don't think will help very much. – Jeremy May 04 '12 at 01:13
  • Yes, red means its blocking. Thanks for making the sample example, I was trying to get your thoughts but your doing so much work, I really appreciate it and I'm sorry to take your time. But even your sample code blocks. The way I tested it was changed your for loop to 900000000(so it doesn't finish too quickly), then ran it and started Java visualvm(its included with java). Basically in the threads tab it says `monitor` which on yourkit is blocking. 2 or 3 of the 4 threads are blocked at any given time – Lostsoul May 04 '12 at 01:30
  • Actually, when I looked at yourkit it said a few things were blocking it, the sysout was blocking(which makes sense) so i commented out that part of the run so there was no output and reran but am getting the same blocking(2-3 threads blocked) but this time the thing blocking is borrowobject. I read on the mailing list that it maybe blocking when creating a new object, so maybe if the lifespan is increased it helps? just a thought.. – Lostsoul May 04 '12 at 01:44