1 /*
2  * Hunt - A redis client library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11  
12 module hunt.redis.util.Pool;
13 
14 import hunt.redis.Exceptions;
15 
16 import hunt.Exceptions;
17 import hunt.logging.ConsoleLogger;
18 import hunt.util.Common;
19 
20 import hunt.pool.PooledObjectFactory;
21 import hunt.pool.impl.GenericObjectPool;
22 import hunt.pool.impl.GenericObjectPoolConfig;
23 
24 
25 
26 abstract class Pool(T) : Closeable {
27     protected GenericObjectPool!(T) internalPool;
28 
29     /**
30      * Using this constructor means you have to set and initialize the internalPool yourself.
31      */
32     this() {
33     }
34 
35     this(GenericObjectPoolConfig poolConfig, PooledObjectFactory!(T) factory) {
36         initPool(poolConfig, factory);
37     }
38 
39     override
40     void close() {
41         destroy();
42     }
43 
44     bool isClosed() {
45         return this.internalPool.isClosed();
46     }
47 
48     void initPool(GenericObjectPoolConfig poolConfig, PooledObjectFactory!(T) factory) {
49 
50         if (this.internalPool !is null) {
51             try {
52                 closeInternalPool();
53             } catch (Exception e) {
54                 debug warning(e.msg);
55                 version(HUNT_REDIS_DEBUG) warning(e);
56             }
57         }
58 
59         this.internalPool = new GenericObjectPool!(T)(factory, poolConfig);
60     }
61 
62     T getResource() {
63         try {
64             return internalPool.borrowObject();
65         } catch (NoSuchElementException nse) {
66             debug warning(nse.msg);
67             version(HUNT_REDIS_DEBUG) warning(nse);
68             if (nse.next is null) { // The exception was caused by an exhausted pool
69                 throw new RedisExhaustedPoolException(
70                         "Could not get a resource since the pool is exhausted", nse);
71             }
72             // Otherwise, the exception was caused by the implemented activateObject() or ValidateObject()
73             throw new RedisException("Could not get a resource from the pool", nse);
74         } catch (Exception e) {
75             debug warning(e.msg);
76             version(HUNT_REDIS_DEBUG) warning(e);
77             throw new RedisConnectionException("Could not get a resource from the pool", e);
78         }
79     }
80 
81     protected void returnResourceObject(T resource) {
82         if (resource is null) {
83             return;
84         }
85         
86         try {
87             internalPool.returnObject(resource);
88         } catch (Exception e) {
89             debug warning(e.msg);
90             version(HUNT_REDIS_DEBUG) warning(e);
91             throw new RedisException("Could not return the resource to the pool", e);
92         }
93     }
94 
95     protected void returnBrokenResource(T resource) {
96         if (resource !is null) {
97             returnBrokenResourceObject(resource);
98         }
99     }
100 
101     protected void returnResource(T resource) {
102         if (resource !is null) {
103             returnResourceObject(resource);
104         }
105     }
106 
107     void destroy() {
108         closeInternalPool();
109     }
110 
111     protected void returnBrokenResourceObject(T resource) {
112         try {
113             internalPool.invalidateObject(resource);
114         } catch (Exception e) {
115             debug warning(e.msg);
116             version(HUNT_REDIS_DEBUG) warning(e);
117             throw new RedisException("Could not return the broken resource to the pool", e);
118         }
119     }
120 
121     protected void closeInternalPool() {
122         try {
123             internalPool.close();
124         } catch (Exception e) {
125             debug warning(e.msg);
126             version(HUNT_REDIS_DEBUG) warning(e);
127             throw new RedisException("Could not destroy the pool", e);
128         }
129     }
130     
131     /**
132      * Returns the number of instances currently borrowed from this pool.
133      *
134      * @return The number of instances currently borrowed from this pool, -1 if
135      * the pool is inactive.
136      */
137     int getNumActive() {
138         if (poolInactive()) {
139             return -1;
140         }
141 
142         return this.internalPool.getNumActive();
143     }
144     
145     /**
146      * Returns the number of instances currently idle in this pool.
147      *
148      * @return The number of instances currently idle in this pool, -1 if the
149      * pool is inactive.
150      */
151     int getNumIdle() {
152         if (poolInactive()) {
153             return -1;
154         }
155 
156         return this.internalPool.getNumIdle();
157     }
158     
159     /**
160      * Returns an estimate of the number of threads currently blocked waiting for
161      * a resource from this pool.
162      *
163      * @return The number of threads waiting, -1 if the pool is inactive.
164      */
165     int getNumWaiters() {
166         if (poolInactive()) {
167             return -1;
168         }
169 
170         return this.internalPool.getNumWaiters();
171     }
172     
173     /**
174      * Returns the mean waiting time spent by threads to obtain a resource from
175      * this pool.
176      *
177      * @return The mean waiting time, in milliseconds, -1 if the pool is
178      * inactive.
179      */
180     long getMeanBorrowWaitTimeMillis() {
181         if (poolInactive()) {
182             return -1;
183         }
184 
185         return this.internalPool.getMeanBorrowWaitTimeMillis();
186     }
187     
188     /**
189      * Returns the maximum waiting time spent by threads to obtain a resource
190      * from this pool.
191      *
192      * @return The maximum waiting time, in milliseconds, -1 if the pool is
193      * inactive.
194      */
195     long getMaxBorrowWaitTimeMillis() {
196         if (poolInactive()) {
197             return -1;
198         }
199 
200         return this.internalPool.getMaxBorrowWaitTimeMillis();
201     }
202 
203     private bool poolInactive() {
204         return this.internalPool is null || this.internalPool.isClosed();
205     }
206 
207     void addObjects(int count) {
208         try {
209             for (int i = 0; i < count; i++) {
210                 this.internalPool.addObject();
211             }
212         } catch (Exception e) {
213             debug warning(e.msg);
214             version(HUNT_REDIS_DEBUG) warning(e);
215             throw new RedisException("Error trying to add idle objects", e);
216         }
217     }
218 }