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 }