According to http://cxf.apache.org/faq.html#FAQ-AreJAX-WSclientproxiesthreadsafe? , clientproxies are not thread safe so I decided to create a Component which can pool the connections. I do it it by using Singleton design pattern.
What is handled by this Singleton:
What is handled by this Singleton:
- This class creates the connections on the start and then caches them
- If the connection to the server is not available it will wait for 1 second and try again 10 times.
- A scheduler thread runs every 10 minutes to check if an error occurred in between. If an error occurred then all the cached instances are created new.
- This implementation is thread safe
ConnectionCache.java
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.xml.ws.Service; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.logging.log4j.Logger; /** * This keeps the cache of MAX_CONNECTIONS_POOL_SIZE number of * connections and tries to shares them equally amongst the threads. All the * connections are created right at the start and if an error occurs then the * cache is created again. * * * Are JAX-WS client proxies thread safe * According to the JAX-WS spec, * the client proxies are NOT thread safe. To write portable code, you should * treat them as non-thread safe and synchronize access or use a pool of * instances or similar. */ public abstract class ConnectionCache<T extends Service> { private static final Logger logger = org.apache.logging.log4j.LogManager.getLogger(ConnectionCache.class); private static final int MAX_CONNECTIONS_POOL_SIZE = 16; private final BlockingQueue<T> freeConnections = new ArrayBlockingQueue<T>(MAX_CONNECTIONS_POOL_SIZE); private final BlockingQueue<T> usedConnections = new ArrayBlockingQueue<T>(MAX_CONNECTIONS_POOL_SIZE); private int totalConnections = 0; private boolean firstTimeLoad = true; private boolean shutdownCalled = false; private ScheduledExecutorService scheduler; public void init() { logger.info("starting connectionCache"); logger.info("start caching connections"); ;; BasicThreadFactory factory = new BasicThreadFactory.Builder() .namingPattern("connectioncache-scheduler-thread-%d").build(); scheduler = Executors.newScheduledThreadPool(1, factory); scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { initializeCache(); firstTimeLoad = false; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }, 0, 10, TimeUnit.MINUTES); } public void destroy() { shutdownCalled = true; try { scheduler.awaitTermination(1, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * It is important to call release after calling this method to release the connection * @return * @throws Exception * @throws InterruptedException */ public T acquire() throws Exception, InterruptedException { T take; if(totalConnections < MAX_CONNECTIONS_POOL_SIZE){ synchronized (ConnectionCache.class) { if(totalConnections < MAX_CONNECTIONS_POOL_SIZE){ logger.info("service connects, free connections: " + freeConnections.size() + ", used connections: " + usedConnections.size()); T service = tryConnect(); freeConnections.offer(service, 1, TimeUnit.MINUTES); totalConnections++; logger.info("connect done, free connections: " + freeConnections.size() + ", used connections: " + usedConnections.size()); } } } // a thread waits a maximum 13 minutes for a connection take = freeConnections.poll(13, TimeUnit.MINUTES); if (take == null) { throw new Exception("connection not avaliable for 13 minutes so aborting connection wait!"); } usedConnections.offer(take); // } return take; } public void release(T service){ try { boolean remove = usedConnections.remove(service); if(remove){ // this will make sure that if a cleanup has been done // the removed objects are not added to the connections cache freeConnections.offer(service,1, TimeUnit.MINUTES); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } private void initializeCache() throws InterruptedException { try { loadCache(); logger.info(firstTimeLoad ? "connections creation finished successfully!" : "connections checked successfully!"); } catch (Exception e) { logger.error("error while initializing the cache"); } } private void loadCache() throws Exception { logger.info(firstTimeLoad ? "create and cache service connections" : "checking connections!" ); for (int i = 0; i < MAX_CONNECTIONS_POOL_SIZE; i++) { if(shutdownCalled){ return; } release(acquire()); } } public void refreshCache() { usedConnections.clear(); freeConnections.clear(); totalConnections =0; } private T tryConnect() throws Exception { boolean connect = true; int tryNum = 0; T service = null; while (connect && !Thread.currentThread().isInterrupted() && !shutdownCalled) { try { service = create(); connect = false; } catch (Exception e) { tryNum = tryReconnect(tryNum, e); } } return service; } private int tryReconnect(int tryNum, Exception e) throws Exception { logger.warn(Thread.currentThread().getName() + " service service not available! : " + e); // try 10 times, if if (tryNum++ < 10) { try { logger.warn(Thread.currentThread().getName() + " wait 1 second"); Thread.sleep(1000); } catch (InterruptedException f) { // restore interrupt Thread.currentThread().interrupt(); } } else { logger.warn(" service could not connect, number of times tried: " + (tryNum - 1)); refreshCache(); throw new Exception(e); } logger.info(" try reconnect number: " + tryNum); return tryNum; } protected abstract T create() ; }
So you can use this as your base class and have to implement only the create method for your service.
Here in an example of a sub class:
MyServiceConnectionCache.java
import com.tak.ws.MyService; public class MyServiceConnectionCache extends ConnectionCache<MyService> { private static MyServiceConnectionCache myServiceConnectionCache = new MyServiceConnectionCache(); public static MyServiceConnectionCache getInstance(){ return myServiceConnectionCache; } private MyServiceConnectionCache() { } @Override protected MyService create() { return new MyService(); } }
Here is the Test for testing the service is working
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import com.tak.ws.MyService; public class Test { public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(100); for(int i = 0; i < 100; i++){ executor.execute(new Runnable() { @Override public void run() { MyServiceConnectionCache myServiceConnectionCache = MyServiceConnectionCache.getInstance(); MyService service = null; try { service = myServiceConnectionCache.acquire(); System.out.println(service.getMyServicePort().getAllLanguages()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ if(service != null){ myServiceConnectionCache.release(service); } } } }); } executor.shutdown(); executor.awaitTermination(5, TimeUnit.MINUTES); } }
And the console output for the above test :
16:22:47.661 [pool-1-thread-1] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 0 16:22:47.832 [pool-1-thread-1] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 0 16:22:47.832 [pool-1-thread-4] INFO ConnectionCache:92 - service connects, free connections: 1, used connections: 0 16:22:47.875 [pool-1-thread-4] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 1 16:22:47.875 [pool-1-thread-20] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 2 16:22:47.925 [pool-1-thread-20] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 2 16:22:47.933 [pool-1-thread-15] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 3 16:22:47.972 [pool-1-thread-15] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 3 16:22:47.972 [pool-1-thread-12] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 4 16:22:48.014 [pool-1-thread-12] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 4 16:22:48.015 [pool-1-thread-7] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 5 16:22:48.073 [pool-1-thread-7] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 5 16:22:48.075 [pool-1-thread-6] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 6 16:22:48.119 [pool-1-thread-6] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 6 16:22:48.119 [pool-1-thread-13] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 7 16:22:48.159 [pool-1-thread-13] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 7 16:22:48.159 [pool-1-thread-16] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 8 16:22:48.194 [pool-1-thread-16] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 8 16:22:48.194 [pool-1-thread-21] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 9 16:22:48.228 [pool-1-thread-21] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 9 16:22:48.229 [pool-1-thread-24] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 10 16:22:48.260 [pool-1-thread-24] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 10 16:22:48.260 [pool-1-thread-23] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 11 16:22:48.292 [pool-1-thread-23] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 11 16:22:48.308 [pool-1-thread-28] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 12 16:22:48.345 [pool-1-thread-28] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 12 16:22:48.345 [pool-1-thread-31] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 13 16:22:48.383 [pool-1-thread-31] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 13 16:22:48.383 [pool-1-thread-29] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 14 16:22:48.415 [pool-1-thread-29] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 14 16:22:48.416 [pool-1-thread-32] INFO ConnectionCache:92 - service connects, free connections: 0, used connections: 15 16:22:48.449 [pool-1-thread-32] INFO ConnectionCache:96 - connect done, free connections: 1, used connections: 15 [FRENCH, ENGLISH, RUSSIAN] [FRENCH, ENGLISH, RUSSIAN] [FRENCH, ENGLISH, RUSSIAN] [FRENCH, ENGLISH, RUSSIAN] [FRENCH, ENGLISH, RUSSIAN] [FRENCH, ENGLISH, RUSSIAN] [FRENCH, ENGLISH, RUSSIAN] [FRENCH, ENGLISH, RUSSIAN] [FRENCH, ENGLISH, RUSSIAN] [FRENCH, ENGLISH, RUSSIAN] .........
Please drop your comments suggestions!