1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 module hunt.pool.impl.DefaultPooledObject;
18 
19 import hunt.pool.impl.CallStack;
20 import hunt.pool.impl.CallStackUtils;
21 import hunt.pool.impl.NoOpCallStack;
22 
23 import hunt.pool.PooledObject;
24 import hunt.pool.PooledObjectState;
25 import hunt.pool.TrackedUse;
26 
27 import hunt.collection;
28 import hunt.concurrency.atomic.AtomicHelper;
29 import hunt.Exceptions;
30 import hunt.util.StringBuilder;
31 import hunt.util.DateTime;
32 
33 import std.algorithm;
34 import std.conv;
35 
36 // import java.io.PrintWriter;
37 // import java.util.Deque;
38 
39 /**
40  * This wrapper is used to track the additional information, such as state, for
41  * the pooled objects.
42  * <p>
43  * This class is intended to be thread-safe.
44  * </p>
45  *
46  * @param <T> the type of object in the pool
47  *
48  */
49 class DefaultPooledObject(T) : PooledObject!(T) {
50 
51     private T object;
52     private PooledObjectState state = PooledObjectState.IDLE; // @GuardedBy("this") to ensure transitions are valid
53     private long createTime; // = DateTime.currentTimeMillis();
54     private shared long lastBorrowTime;
55     private shared long lastUseTime;
56     private shared long lastReturnTime;
57     private shared bool logAbandoned = false;
58     private CallStack borrowedBy;
59     private CallStack usedBy;
60     private shared long borrowedCount = 0;
61 
62     /**
63      * Create a new instance that wraps the provided object so that the pool can
64      * track the state of the pooled object.
65      *
66      * @param object The object to wrap
67      */
68     this(T object) {
69         createTime = DateTime.currentTimeMillis();
70         lastBorrowTime = createTime;
71         lastUseTime = createTime;
72         lastReturnTime = createTime;
73         borrowedBy = NoOpCallStack.INSTANCE;
74         usedBy = NoOpCallStack.INSTANCE;
75         this.object = object;
76     }
77 
78     
79     TypeInfo objectType() {
80         return typeid(object);
81     }
82 
83     string objectToString() {
84         return (cast(Object)object).toString();
85     }
86 
87     override T getObject() {
88         return object;
89     }
90 
91     override long getCreateTime() {
92         return createTime;
93     }
94 
95     override long getActiveTimeMillis() {
96         // Take copies to avoid threading issues
97         long rTime = lastReturnTime;
98         long bTime = lastBorrowTime;
99 
100         if (rTime > bTime) {
101             return rTime - bTime;
102         }
103         return DateTime.currentTimeMillis() - bTime;
104     }
105 
106     override long getIdleTimeMillis() {
107         long elapsed = DateTime.currentTimeMillis() - lastReturnTime;
108         // elapsed may be negative if:
109         // - another thread updates lastReturnTime during the calculation window
110         // - DateTime.currentTimeMillis() is not monotonic (e.g. system time is set back)
111         return elapsed >= 0 ? elapsed : 0;
112     }
113 
114     override long getLastBorrowTime() {
115         return lastBorrowTime;
116     }
117 
118     override long getLastReturnTime() {
119         return lastReturnTime;
120     }
121 
122     /**
123      * Get the number of times this object has been borrowed.
124      * @return The number of times this object has been borrowed.
125      */
126     long getBorrowedCount() {
127         return borrowedCount;
128     }
129 
130     /**
131      * Return an estimate of the last time this object was used.  If the class
132      * of the pooled object implements {@link TrackedUse}, what is returned is
133      * the maximum of {@link TrackedUse#getLastUsed()} and
134      * {@link #getLastBorrowTime()}; otherwise this method gives the same
135      * value as {@link #getLastBorrowTime()}.
136      *
137      * @return the last time this object was used
138      */
139     override long getLastUsedTime() {
140         TrackedUse tu = cast(TrackedUse) object;
141         if (tu !is null) {
142             return max(tu.getLastUsed(), lastUseTime);
143         }
144         return lastUseTime;
145     }
146 
147     override int opCmp(IPooledObject other) {
148         long lastActiveDiff = this.getLastReturnTime() - other.getLastReturnTime();
149         if (lastActiveDiff == 0) {
150             // Make sure the natural ordering is broadly consistent with equals
151             // although this will break down if distinct objects have the same
152             // identity hash code.
153             // see java.lang.Comparable Javadocs
154             // return System.identityHashCode(this) - System.identityHashCode(other);
155             return cast(int)(this.toHash() - other.toHash());
156         }
157         // handle int overflow
158         return cast(int) min(max(lastActiveDiff, int.min), int.max);
159     }
160 
161     override string toString() {
162         StringBuilder result = new StringBuilder();
163         result.append("Object: ");
164         result.append((cast(Object) object).toString());
165         result.append(", State: ");
166         synchronized (this) {
167             result.append(state.to!string());
168         }
169         return result.toString();
170         // TODO add other attributes
171     }
172 
173     override bool startEvictionTest() { // synchronized 
174         if (state == PooledObjectState.IDLE) {
175             state = PooledObjectState.EVICTION;
176             return true;
177         }
178 
179         return false;
180     }
181 
182     override bool endEvictionTest( // synchronized 
183             Deque!(IPooledObject) idleQueue) {
184         if (state == PooledObjectState.EVICTION) {
185             state = PooledObjectState.IDLE;
186             return true;
187         } else if (state == PooledObjectState.EVICTION_RETURN_TO_HEAD) {
188             state = PooledObjectState.IDLE;
189             if (!idleQueue.offerFirst(this)) {
190                 // TODO - Should never happen
191             }
192         }
193 
194         return false;
195     }
196 
197     /**
198      * Allocates the object.
199      *
200      * @return {@code true} if the original state was {@link PooledObjectState#IDLE IDLE}
201      */
202     override bool allocate() { // synchronized
203         if (state == PooledObjectState.IDLE) {
204             state = PooledObjectState.ALLOCATED;
205             lastBorrowTime = DateTime.currentTimeMillis();
206             lastUseTime = lastBorrowTime;
207             borrowedCount.increment();
208             if (logAbandoned) {
209                 borrowedBy.fillInStackTrace();
210             }
211             return true;
212         } else if (state == PooledObjectState.EVICTION) {
213             // TODO Allocate anyway and ignore eviction test
214             state = PooledObjectState.EVICTION_RETURN_TO_HEAD;
215             return false;
216         }
217         // TODO if validating and testOnBorrow == true then pre-allocate for
218         // performance
219         return false;
220     }
221 
222     /**
223      * Deallocates the object and sets it {@link PooledObjectState#IDLE IDLE}
224      * if it is currently {@link PooledObjectState#ALLOCATED ALLOCATED}.
225      *
226      * @return {@code true} if the state was {@link PooledObjectState#ALLOCATED ALLOCATED}
227      */
228     override bool deallocate() { // synchronized
229         if (state == PooledObjectState.ALLOCATED || state == PooledObjectState.RETURNING) {
230             state = PooledObjectState.IDLE;
231             lastReturnTime = DateTime.currentTimeMillis();
232             borrowedBy.clear();
233             return true;
234         }
235 
236         return false;
237     }
238 
239     /**
240      * Sets the state to {@link PooledObjectState#INVALID INVALID}
241      */
242     override void invalidate() { // synchronized
243         state = PooledObjectState.INVALID;
244     }
245 
246     override void use() {
247         lastUseTime = DateTime.currentTimeMillis();
248         usedBy.fillInStackTrace();
249     }
250 
251     // override
252     // void printStackTrace(PrintWriter writer) {
253     //     bool written = borrowedBy.printStackTrace(writer);
254     //     written |= usedBy.printStackTrace(writer);
255     //     if (written) {
256     //         writer.flush();
257     //     }
258     // }
259 
260     /**
261      * Returns the state of this object.
262      * @return state
263      */
264     override PooledObjectState getState() { // synchronized
265         return state;
266     }
267 
268     /**
269      * Marks the pooled object as abandoned.
270      */
271     override void markAbandoned() { // synchronized
272         state = PooledObjectState.ABANDONED;
273     }
274 
275     /**
276      * Marks the object as returning to the pool.
277      */
278     override void markReturning() { // synchronized
279         state = PooledObjectState.RETURNING;
280     }
281 
282     override void setLogAbandoned(bool logAbandoned) {
283         this.logAbandoned = logAbandoned;
284     }
285 
286     /**
287      * Configures the stack trace generation strategy based on whether or not fully
288      * detailed stack traces are required. When set to false, abandoned logs may
289      * only include caller class information rather than method names, line numbers,
290      * and other normal metadata available in a full stack trace.
291      *
292      * @param requireFullStackTrace the new configuration setting for abandoned object
293      *                              logging
294      */
295     // TODO: uncomment below in 3.0
296     // override
297     void setRequireFullStackTrace(bool requireFullStackTrace) {
298         implementationMissing(false);
299         // borrowedBy = CallStackUtils.newCallStack("'Pooled object created' " ~
300         //     "yyyy-MM-dd HH:mm:ss Z 'by the following code has not been returned to the pool:'",
301         //     true, requireFullStackTrace);
302         // usedBy = CallStackUtils.newCallStack("The last code to use this object was:",
303         //     false, requireFullStackTrace);
304     }
305 
306     bool opEquals(IPooledObject obj) {
307         return opEquals(cast(Object) obj);
308     }
309 
310     override bool opEquals(Object obj) {
311         return super.opEquals(obj);
312     }
313 
314 }