/* | |
* Licensed to the Apache Software Foundation (ASF) under one or more | |
* contributor license agreements. See the NOTICE file distributed with | |
* this work for additional information regarding copyright ownership. | |
* The ASF licenses this file to You under the Apache License, Version 2.0 | |
* (the "License"); you may not use this file except in compliance with | |
* the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.apache.commons.dbcp2; | |
import java.lang.ref.WeakReference; | |
import java.sql.SQLException; | |
import java.time.Instant; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.function.Consumer; | |
import org.apache.commons.pool2.TrackedUse; | |
/** | |
* Tracks connection usage for recovering and reporting abandoned connections. | |
* <p> | |
* The JDBC Connection, Statement, and ResultSet classes extend this class. | |
* </p> | |
* | |
* @since 2.0 | |
*/ | |
public class AbandonedTrace implements TrackedUse, AutoCloseable { | |
static void add(final AbandonedTrace receiver, final AbandonedTrace trace) { | |
if (receiver != null) { | |
receiver.addTrace(trace); | |
} | |
} | |
/** A list of objects created by children of this object. */ | |
private final List<WeakReference<AbandonedTrace>> traceList = new ArrayList<>(); | |
/** Last time this connection was used. */ | |
private volatile Instant lastUsedInstant = Instant.EPOCH; | |
/** | |
* Creates a new AbandonedTrace without config and without doing abandoned tracing. | |
*/ | |
public AbandonedTrace() { | |
init(null); | |
} | |
/** | |
* Constructs a new AbandonedTrace with a parent object. | |
* | |
* @param parent | |
* AbandonedTrace parent object. | |
*/ | |
public AbandonedTrace(final AbandonedTrace parent) { | |
init(parent); | |
} | |
/** | |
* Adds an object to the list of objects being traced. | |
* | |
* @param trace | |
* AbandonedTrace object to add. | |
*/ | |
protected void addTrace(final AbandonedTrace trace) { | |
synchronized (this.traceList) { | |
this.traceList.add(new WeakReference<>(trace)); | |
} | |
setLastUsed(); | |
} | |
/** | |
* Clears the list of objects being traced by this object. | |
*/ | |
protected void clearTrace() { | |
synchronized (this.traceList) { | |
this.traceList.clear(); | |
} | |
} | |
/** | |
* Subclasses can implement this nop. | |
* | |
* @throws SQLException Ignored here, for subclasses. | |
* @since 2.10.0 | |
*/ | |
@Override | |
public void close() throws SQLException { | |
// nop | |
} | |
/** | |
* Closes this resource and if an exception is caught, then calls {@code exceptionHandler}. | |
* | |
* @param exceptionHandler Consumes exception thrown closing this resource. | |
* @since 2.10.0 | |
*/ | |
protected void close(final Consumer<Exception> exceptionHandler) { | |
Utils.close(this, exceptionHandler); | |
} | |
/** | |
* Gets the last time this object was used in milliseconds. | |
* | |
* @return long time in milliseconds. | |
*/ | |
@Override | |
@Deprecated | |
public long getLastUsed() { | |
return lastUsedInstant.toEpochMilli(); | |
} | |
@Override | |
public Instant getLastUsedInstant() { | |
return lastUsedInstant; | |
} | |
/** | |
* Gets a list of objects being traced by this object. | |
* | |
* @return List of objects. | |
*/ | |
protected List<AbandonedTrace> getTrace() { | |
final int size = traceList.size(); | |
if (size == 0) { | |
return Collections.emptyList(); | |
} | |
final ArrayList<AbandonedTrace> result = new ArrayList<>(size); | |
synchronized (this.traceList) { | |
final Iterator<WeakReference<AbandonedTrace>> iter = traceList.iterator(); | |
while (iter.hasNext()) { | |
final AbandonedTrace trace = iter.next().get(); | |
if (trace == null) { | |
// Clean-up since we are here anyway | |
iter.remove(); | |
} else { | |
result.add(trace); | |
} | |
} | |
} | |
return result; | |
} | |
/** | |
* Initializes abandoned tracing for this object. | |
* | |
* @param parent | |
* AbandonedTrace parent object. | |
*/ | |
private void init(final AbandonedTrace parent) { | |
add(parent, this); | |
} | |
/** | |
* Removes this object the source object is tracing. | |
* | |
* @param source The object tracing | |
* @since 2.7.0 | |
*/ | |
protected void removeThisTrace(final Object source) { | |
if (source instanceof AbandonedTrace) { | |
AbandonedTrace.class.cast(source).removeTrace(this); | |
} | |
} | |
/** | |
* Removes a child object this object is tracing. | |
* | |
* @param trace | |
* AbandonedTrace object to remove. | |
*/ | |
protected void removeTrace(final AbandonedTrace trace) { | |
synchronized (this.traceList) { | |
final Iterator<WeakReference<AbandonedTrace>> iter = traceList.iterator(); | |
while (iter.hasNext()) { | |
final AbandonedTrace traceInList = iter.next().get(); | |
if (trace != null && trace.equals(traceInList)) { | |
iter.remove(); | |
break; | |
} | |
if (traceInList == null) { | |
// Clean-up since we are here anyway | |
iter.remove(); | |
} | |
} | |
} | |
} | |
/** | |
* Sets the time this object was last used to the current time in milliseconds. | |
*/ | |
protected void setLastUsed() { | |
lastUsedInstant = Instant.now(); | |
} | |
/** | |
* Sets the instant this object was last used. | |
* | |
* @param lastUsedInstant | |
* instant. | |
* @since 2.10.0 | |
*/ | |
protected void setLastUsed(final Instant lastUsedInstant) { | |
this.lastUsedInstant = lastUsedInstant; | |
} | |
/** | |
* Sets the time in milliseconds this object was last used. | |
* | |
* @param lastUsedMillis | |
* time in milliseconds. | |
* @deprecated Use {@link #setLastUsed(Instant)} | |
*/ | |
@Deprecated | |
protected void setLastUsed(final long lastUsedMillis) { | |
this.lastUsedInstant = Instant.ofEpochMilli(lastUsedMillis); | |
} | |
} |