diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/util/ConnectionPoolObjectsUtils.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/util/ConnectionPoolObjectsUtils.java
index 365ec0e6419..6b2d7dc71ed 100644
--- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/util/ConnectionPoolObjectsUtils.java
+++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/util/ConnectionPoolObjectsUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022, 2025 Contributors to the Eclipse Foundation
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
@@ -395,7 +395,9 @@ private static boolean toBoolean(Object prop, boolean defaultVal) {
public static int getTransactionSupportFromRaXml(String rarName) throws ConnectorRuntimeException {
ConnectorDescriptor descriptor = ConnectorRuntime.getRuntime().getConnectorDescriptor(rarName);
- String txSupport = descriptor.getOutboundResourceAdapter().getTransSupport();
+ String txSupport = descriptor == null || descriptor.getOutboundResourceAdapter() == null
+ ? null
+ : descriptor.getOutboundResourceAdapter().getTransSupport();
return parseTransactionSupportString(txSupport);
}
}
diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/AbstractConnectorAllocator.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/AbstractConnectorAllocator.java
index c6eb4b65bab..1a2e4e43158 100644
--- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/AbstractConnectorAllocator.java
+++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/AbstractConnectorAllocator.java
@@ -52,7 +52,7 @@
* @author Sivakumar Thyagarajan
*/
public abstract class AbstractConnectorAllocator implements ResourceAllocator {
- protected final static Logger LOG = LogDomains.getLogger(AbstractConnectorAllocator.class,LogDomains.RSR_LOGGER);
+ protected static final Logger LOG = LogDomains.getLogger(AbstractConnectorAllocator.class,LogDomains.RSR_LOGGER);
protected PoolManager poolMgr;
protected ResourceSpec spec;
diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/LocalTxConnectorAllocator.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/LocalTxConnectorAllocator.java
index d95e4f9bcb1..f85def83867 100644
--- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/LocalTxConnectorAllocator.java
+++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/allocator/LocalTxConnectorAllocator.java
@@ -40,6 +40,8 @@
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;
+import static java.util.logging.Level.FINEST;
+
/**
* LocalTransaction Connector Allocator, used for transaction level:
* {@link com.sun.appserv.connectors.internal.api.ConnectorConstants#LOCAL_TRANSACTION_INT}.
@@ -51,7 +53,7 @@ public class LocalTxConnectorAllocator extends AbstractConnectorAllocator {
private static final String COMMIT = "COMMIT";
private static final String ROLLBACK = "ROLLBACK";
- private static String transactionCompletionMode = System
+ private static final String TX_COMPLETION_MODE = System
.getProperty("com.sun.enterprise.in-progress-local-transaction.completion-mode");
private final boolean shareable;
@@ -69,6 +71,10 @@ public LocalTxConnectorAllocator(PoolManager poolMgr,
this.shareable = shareable;
}
+ @Override
+ public boolean shareableWithinComponent() {
+ return shareable;
+ }
@Override
public ResourceHandle createResource() throws PoolingException {
@@ -90,68 +96,56 @@ public ResourceHandle createResource() throws PoolingException {
}
@Override
- public void fillInResourceObjects(ResourceHandle resource)
- throws PoolingException {
+ public void fillInResourceObjects(ResourceHandle handle) throws PoolingException {
try {
- ManagedConnection mc = resource.getResource();
+ ManagedConnection mc = handle.getResource();
Object con = mc.getConnection(subject, reqInfo);
- ConnectorXAResource xares = (ConnectorXAResource) resource.getXAResource();
+ ConnectorXAResource xares = (ConnectorXAResource) handle.getXAResource();
xares.setUserHandle(con);
- resource.fillInResourceObjects(con, xares);
+ handle.fillInResourceObjects(con, xares);
} catch (ResourceException ex) {
throw new PoolingException(ex);
}
}
@Override
- public void destroyResource(ResourceHandle resource)
- throws PoolingException {
+ public void destroyResource(ResourceHandle handle) throws PoolingException {
try {
- ManagedConnection mc = resource.getResource();
- XAResource xares = resource.getXAResource();
- forceTransactionCompletion(xares);
- mc.destroy();
- LOG.finest("destroyResource for LocalTxConnectorAllocator done");
-
+ ManagedConnection connection = handle.getResource();
+ XAResource xaResource = handle.getXAResource();
+ forceTransactionCompletion(xaResource);
+ connection.destroy();
+ LOG.log(FINEST, "Connection was destroyed: {0}", connection);
} catch (Exception ex) {
throw new PoolingException(ex);
}
}
- @Override
- public boolean shareableWithinComponent() {
- return shareable;
- }
- private void forceTransactionCompletion(XAResource xares) throws SystemException {
- if(transactionCompletionMode != null){
- if(xares instanceof ConnectorXAResource){
- ConnectorXAResource connectorXARes = (ConnectorXAResource)xares;
- JavaEETransaction j2eetran = connectorXARes.getAssociatedTransaction();
- if(j2eetran != null && j2eetran.isLocalTx()){
- if(j2eetran.getStatus() == (Status.STATUS_ACTIVE)){
- try{
- if(transactionCompletionMode.equalsIgnoreCase(COMMIT)){
- if(LOG.isLoggable(Level.FINEST)){
- LOG.log(Level.FINEST,"Transaction Completion Mode for LocalTx resource is " +
- "set as COMMIT, committing transaction");
- }
- j2eetran.commit();
- }else if(transactionCompletionMode.equalsIgnoreCase(ROLLBACK)){
- if(LOG.isLoggable(Level.FINEST)){
- LOG.log(Level.FINEST,"Transaction Completion Mode for LocalTx resource is " +
- "set as ROLLBACK, rolling back transaction");
- }
- j2eetran.rollback();
- }else{
- LOG.log(Level.WARNING,"Unknown transaction completion mode, no action made");
- }
- }catch(Exception e){
- LOG.log(Level.WARNING, "Failure while forcibily completing an incomplete, " +
- "local transaction ", e);
- }
- }
+ private void forceTransactionCompletion(XAResource xaResource) throws SystemException {
+ if (TX_COMPLETION_MODE == null) {
+ return;
+ }
+ if (xaResource instanceof ConnectorXAResource) {
+ ConnectorXAResource connectorXARes = (ConnectorXAResource) xaResource;
+ JavaEETransaction j2eetran = connectorXARes.getAssociatedTransaction();
+ if (j2eetran == null || !j2eetran.isLocalTx() || j2eetran.getStatus() != Status.STATUS_ACTIVE) {
+ return;
+ }
+ try {
+ if (COMMIT.equalsIgnoreCase(TX_COMPLETION_MODE)) {
+ LOG.log(FINEST, "Transaction Completion Mode for LocalTx resource is set as COMMIT,"
+ + " committing transaction");
+ j2eetran.commit();
+ } else if (ROLLBACK.equalsIgnoreCase(TX_COMPLETION_MODE)) {
+ LOG.log(FINEST, "Transaction Completion Mode for LocalTx resource is set as ROLLBACK,"
+ + " rolling back transaction");
+ j2eetran.rollback();
+ } else {
+ LOG.log(Level.WARNING, "Unknown transaction completion mode, no action made");
}
+ } catch (Exception e) {
+ LOG.log(Level.WARNING, "Failure while forcibily completing an incomplete, local transaction ", e);
}
}
}
diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/AssocWithThreadResourcePool.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/AssocWithThreadResourcePool.java
index 0daa3abe621..fae1af2816d 100644
--- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/AssocWithThreadResourcePool.java
+++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/AssocWithThreadResourcePool.java
@@ -199,15 +199,15 @@ protected synchronized void freeUnenlistedResource(ResourceHandle resourceHandle
/**
* destroys the resource
*
- * @param resourceHandle resource to be destroyed
+ * @param handle resource to be destroyed
*/
@Override
- public void deleteResource(ResourceHandle resourceHandle) {
+ public void deleteResource(ResourceHandle handle) {
try {
- super.deleteResource(resourceHandle);
+ super.deleteResource(handle);
} finally {
- if (resourceHandle instanceof AssocWithThreadResourceHandle) {
- ((AssocWithThreadResourceHandle) resourceHandle).setUnusable();
+ if (handle instanceof AssocWithThreadResourceHandle) {
+ ((AssocWithThreadResourceHandle) handle).setUnusable();
}
}
}
@@ -215,18 +215,18 @@ public void deleteResource(ResourceHandle resourceHandle) {
/**
* to associate a resource with the thread
*
- * @param h ResourceHandle
+ * @param handle ResourceHandle
*/
- private void setInThreadLocal(AssocWithThreadResourceHandle h) {
- if (h == null) {
+ private void setInThreadLocal(AssocWithThreadResourceHandle handle) {
+ if (handle == null) {
return;
}
- h.lock();
+ handle.lock();
try {
- h.setAssociated(true);
- localResource.set(h);
+ handle.setAssociated(true);
+ localResource.set(handle);
} finally {
- h.unlock();
+ handle.unlock();
}
}
@@ -250,7 +250,7 @@ private ResourceHandle resolvePossibleRemoval(ResourceHandle handle) {
}
}
- private synchronized ResourceHandle searchFreeUnenlisted(ResourceAllocator alloc) {
+ private synchronized ResourceHandle searchFreeUnenlisted(ResourceAllocator allocator) {
for (ResourceHandle handle : dataStructure.getAllResources()) {
handle.lock();
try {
@@ -262,7 +262,7 @@ private synchronized ResourceHandle searchFreeUnenlisted(ResourceAllocator alloc
if (handle.getResourceState().isEnlisted() || handle.getResourceState().isBusy()
|| handle.hasConnectionErrorOccurred() || ((AssocWithThreadResourceHandle) handle).isUnusable()
- || !matchConnection(handle, alloc)) {
+ || !matchConnection(handle, allocator)) {
continue;
}
setResourceStateToBusy(handle);
diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/ConnectionPool.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/ConnectionPool.java
index d7b4ba6e077..58bbfa87aa1 100644
--- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/ConnectionPool.java
+++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/ConnectionPool.java
@@ -31,13 +31,13 @@
import com.sun.enterprise.resource.pool.waitqueue.PoolWaitQueue;
import com.sun.enterprise.resource.pool.waitqueue.PoolWaitQueueFactory;
import com.sun.enterprise.transaction.api.JavaEETransaction;
-import com.sun.logging.LogDomains;
import jakarta.resource.ResourceException;
import jakarta.resource.spi.ManagedConnection;
import jakarta.resource.spi.RetryableUnavailableException;
import jakarta.transaction.Transaction;
+import java.lang.System.Logger;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
@@ -45,7 +45,6 @@
import java.util.Set;
import java.util.Timer;
import java.util.concurrent.locks.ReentrantLock;
-import java.util.logging.Logger;
import javax.naming.NamingException;
@@ -53,11 +52,10 @@
import static com.sun.appserv.connectors.internal.spi.BadConnectionEventListener.POOL_RECONFIGURED_ERROR_CODE;
import static com.sun.enterprise.connectors.service.ConnectorAdminServiceUtils.getReservePrefixedJNDINameForPool;
-import static java.util.logging.Level.FINE;
-import static java.util.logging.Level.FINEST;
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.SEVERE;
-import static java.util.logging.Level.WARNING;
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.ERROR;
+import static java.lang.System.Logger.Level.TRACE;
+import static java.lang.System.Logger.Level.WARNING;
/**
* Connection Pool for Connector & JDBC resources
@@ -66,7 +64,7 @@
*/
public class ConnectionPool implements ResourcePool, ConnectionLeakListener, ResourceHandler, PoolProperties {
- private static final Logger LOG = LogDomains.getLogger(ConnectionPool.class, LogDomains.RSR_LOGGER);
+ private static final Logger LOG = System.getLogger(ConnectionPool.class.getName());
// pool life-cycle config properties
/**
@@ -256,7 +254,7 @@ public ConnectionPool(PoolInfo poolInfo, Hashtable, ?> env) throws PoolingExce
poolTxHelper = new PoolTxHelper(this.poolInfo);
gateway = ResourceGateway.getInstance(resourceGatewayClass);
- LOG.log(FINE, "Connection Pool: {0}", this.poolInfo);
+ LOG.log(DEBUG, "Connection Pool: {0}", this.poolInfo);
}
protected void initializePoolWaitQueue() throws PoolingException {
@@ -275,6 +273,19 @@ protected void initializeResourceSelectionStrategy() {
// do nothing
}
+
+ /**
+ * Overridden in {@link AssocWithThreadResourcePool} to fetch the resource cached
+ * in the ThreadLocal. Here it simply returns null.
+ *
+ * @param spec the ResourceSpec used to locate the correct resource pool
+ * @param alloc ResourceAllocator to create a resource
+ * @return ResourceHandle resource from ThreadLocal
+ */
+ protected ResourceHandle prefetch(ResourceSpec spec, ResourceAllocator alloc) {
+ return null;
+ }
+
private void setPoolConfiguration(Hashtable, ?> env) throws PoolingException {
ConnectorConnectionPool poolResource = getPoolConfigurationFromJndi(env);
@@ -321,14 +332,18 @@ protected ConnectorConnectionPool getPoolConfigurationFromJndi(Hashtable, ?> e
}
}
- // This method does not need to be synchronized since all caller methods are,
- // but it does not hurt. Just to be safe.
- protected synchronized void initPool(ResourceAllocator allocator) throws PoolingException {
+ /**
+ * Synchronized initialization of the pool.
+ *
+ * @param resourceAllocator
+ * @throws PoolingException
+ */
+ protected synchronized void ensurePoolInitialized(ResourceAllocator resourceAllocator) throws PoolingException {
if (poolInitialized) {
return;
}
-
- this.allocator = allocator;
+ LOG.log(DEBUG, "Initializing pool {0} with resource allocator: {1}", poolInfo, resourceAllocator);
+ this.allocator = resourceAllocator;
createResources(this.allocator, steadyPoolSize - dataStructure.getResourcesSize());
@@ -367,7 +382,7 @@ protected void scheduleResizerTask() {
}
resizerTaskTimer.scheduleAtFixedRate(resizerTask, idletime, idletime);
- LOG.log(FINE, "Scheduled resizer task with the idle time {0} ms", idletime);
+ LOG.log(DEBUG, "Scheduled resizer task with the idle time {0} ms", idletime);
}
protected Resizer initializeResizer() {
@@ -390,7 +405,7 @@ private void addResource(ResourceAllocator alloc) throws PoolingException {
}
}
- LOG.log(FINE, "Pool: resource added");
+ LOG.log(DEBUG, "Pool: resource added");
}
/**
@@ -427,181 +442,147 @@ protected void setResourceStateToBusy(ResourceHandle resourceHandle) {
* max-connection-wait-time-in-millis has expired.
*/
@Override
- public ResourceHandle getResource(ResourceSpec spec, ResourceAllocator alloc, Transaction transaction) throws PoolingException, RetryableUnavailableException {
- // Note: this method should not be synchronized or the
- // startTime would be incorrect for threads waiting to enter
-
- /*
- * Here are all the comments for the method put together for easy reference. 1. // - Try to get a free resource. Note:
- * internalGetResource() // will create a new resource if none is free and the max has // not been reached. // - If
- * can't get one, get on the wait queue. // - Repeat this until maxWaitTime expires. // - If maxWaitTime == 0, repeat
- * indefinitely.
- *
- * 2. //the doFailAllConnectionsProcessing method would already //have been invoked by now. //We simply go ahead and
- * create a new resource here //from the allocator that we have and adjust the resources //list accordingly so as to not
- * exceed the maxPoolSize ever //(i.e if steadyPoolSize == maxPoolSize ) ///Also since we are creating the resource out
- * of the allocator //that we came into this method with, we need not worry about //matching
- */
- ResourceHandle result = null;
-
- long startTime = System.currentTimeMillis();
- long elapsedWaitTime;
- long remainingWaitTime = 0;
+ public ResourceHandle getResource(ResourceSpec spec, ResourceAllocator alloc, Transaction transaction)
+ throws PoolingException, RetryableUnavailableException {
+ ResourceHandle handle = queueForResource(spec, alloc, transaction);
+ if (handle != null) {
+ alloc.fillInResourceObjects(handle);
+ }
+ return handle;
+ }
+ private ResourceHandle queueForResource(ResourceSpec spec, ResourceAllocator alloc, Transaction transaction)
+ throws PoolingException, RetryableUnavailableException {
+ final long startTime = System.currentTimeMillis();
while (true) {
- if (gateway.allowed()) {
- // See comment #1 above
- JavaEETransaction javaEETransaction = ((JavaEETransaction) transaction);
- final Set> resourcesSet = javaEETransaction == null ? null : javaEETransaction.getResources(poolInfo);
-
- // Allow when the pool is not blocked or at-least one resource is
- // already obtained in the current transaction.
- if (!blocked || (resourcesSet != null && !resourcesSet.isEmpty())) {
- try {
- result = internalGetResource(spec, alloc, transaction);
- } finally {
- gateway.acquiredResource();
- }
- }
- }
-
- if (result != null) {
- // got one, return it
+ final ResourceHandle handle = tryToAcquireHandle(spec, alloc, transaction);
+ final long elapsedTime = System.currentTimeMillis() - startTime;
+ if (handle != null) {
+ gateway.acquiredResource();
if (poolLifeCycleListener != null) {
- poolLifeCycleListener.connectionAcquired(result.getId());
- elapsedWaitTime = System.currentTimeMillis() - startTime;
- poolLifeCycleListener.connectionRequestServed(elapsedWaitTime);
- if (LOG.isLoggable(FINE)) {
- LOG.log(FINE,
- "Resource Pool: elapsed time (ms) to get connection for [" + spec + "] : " + elapsedWaitTime);
- }
+ poolLifeCycleListener.connectionAcquired(handle.getId());
+ poolLifeCycleListener.connectionRequestServed(elapsedTime);
+ LOG.log(DEBUG, "Elapsed time to get connection for {0}: {1} ms", spec, elapsedTime);
}
- // got one - seems we are not doing validation or matching
- // return it
- break;
+ return handle;
}
- // did not get a resource.
- if (maxWaitTime > 0) {
- elapsedWaitTime = System.currentTimeMillis() - startTime;
- if (elapsedWaitTime < maxWaitTime) {
- // time has not expired, determine remaining wait time.
- remainingWaitTime = maxWaitTime - elapsedWaitTime;
- } else if (!blocked) {
- // wait time has expired
- if (poolLifeCycleListener != null) {
- poolLifeCycleListener.connectionTimedOut();
+ final long remainingTime = checkRemainingTime(elapsedTime);
+ if (blocked) {
+ waitForReconfiguration();
+ }
+ // add to wait-queue
+ final Object waitMonitor = new Object();
+ if (poolLifeCycleListener != null) {
+ poolLifeCycleListener.connectionRequestQueued();
+ }
+ synchronized (waitMonitor) {
+ waitQueue.addToQueue(waitMonitor);
+ try {
+ LOG.log(TRACE, "Getting on wait queue");
+ waitMonitor.wait(remainingTime);
+ } catch (InterruptedException e) {
+ LOG.log(TRACE, "Waiting interrupted.", e);
+ Thread.currentThread().interrupt();
+ } finally {
+ LOG.log(TRACE, "Removing wait monitor from queue: {0}", waitMonitor);
+ if (waitQueue.removeFromQueue(waitMonitor) && poolLifeCycleListener != null) {
+ poolLifeCycleListener.connectionRequestDequeued();
}
- throw new PoolingException("No available resources and wait time " + maxWaitTime + " ms expired.");
}
}
+ }
+ }
- if (!blocked) {
- // add to wait-queue
- Object waitMonitor = new Object();
- if (poolLifeCycleListener != null) {
- poolLifeCycleListener.connectionRequestQueued();
- }
- synchronized (waitMonitor) {
- waitQueue.addToQueue(waitMonitor);
- try {
- LOG.log(FINE, "Resource Pool: getting on wait queue");
- waitMonitor.wait(remainingWaitTime);
+ private ResourceHandle tryToAcquireHandle(ResourceSpec spec, ResourceAllocator resourceAllocator,
+ Transaction transaction) throws PoolingException {
+ if (!gateway.allowed()) {
+ LOG.log(TRACE, "Returning null because: gateway not allowed.");
+ return null;
+ }
+ final JavaEETransaction jeeTransaction = ((JavaEETransaction) transaction);
+ final Set> resources = jeeTransaction == null ? null : jeeTransaction.getResources(poolInfo);
+ if (blocked && (resources == null || resources.isEmpty())) {
+ LOG.log(TRACE, "Returning null because: blocked and no resources available using JEE Transaction.");
+ return null;
+ }
+ // Allow when the pool is not blocked or at-least one resource is
+ // already obtained in the current transaction.
+ return acquireHandle(spec, resourceAllocator, transaction);
+ }
- } catch (InterruptedException ex) {
- // Could be system shutdown.
- Thread.currentThread().interrupt();
- break;
- }
- // Try to remove in case that the monitor has timed out. We don't expect the queue to grow to great numbers
- // so the overhead for removing inexistant objects is low.
- LOG.log(FINE, "removing wait monitor from queue: {0}", waitMonitor);
+ private ResourceHandle acquireHandle(ResourceSpec resourceSpec, ResourceAllocator resourceAllocator,
+ Transaction transaction) throws PoolingException {
- if (waitQueue.removeFromQueue(waitMonitor)) {
- if (poolLifeCycleListener != null) {
- poolLifeCycleListener.connectionRequestDequeued();
- }
- }
- }
- } else {
- // Add to reconfig-wait-queue
- Object reconfigWaitMonitor = new Object();
- synchronized (reconfigWaitMonitor) {
- reconfigWaitQueue.addToQueue(reconfigWaitMonitor);
- try {
- if (reconfigWaitTime > 0) {
- LOG.log(FINEST, "[DRC] getting into reconfig wait queue for time [{0}]", reconfigWaitTime);
- reconfigWaitMonitor.wait(reconfigWaitTime);
- }
- } catch (InterruptedException ex) {
- // Could be system shutdown.
- Thread.currentThread().interrupt();
- break;
- }
+ ensurePoolInitialized(resourceAllocator);
- // Try to remove in case that the monitor has timed
- // out. We don't expect the queue to grow to great numbers
- // so the overhead for removing inexistent objects is low.
- LOG.log(FINEST, "[DRC] removing wait monitor from reconfig-wait-queue: {0}", reconfigWaitMonitor);
+ LOG.log(DEBUG, "Acquiring handle for {0}", resourceSpec);
- reconfigWaitQueue.removeFromQueue(reconfigWaitMonitor);
+ ResourceHandle handle = getResourceFromTransaction(transaction, resourceAllocator, resourceSpec);
+ if (handle != null) {
+ return handle;
+ }
- LOG.log(FINEST, "[DRC] throwing Retryable-Unavailable-Exception");
- RetryableUnavailableException rue = new RetryableUnavailableException(
- "Pool Reconfigured, Connection Factory can retry the lookup");
- rue.setErrorCode(POOL_RECONFIGURED_ERROR_CODE);
+ handle = prefetch(resourceSpec, resourceAllocator);
+ if (handle != null) {
+ return handle;
+ }
- throw rue;
- }
+ // We didn't get a connection that is already enlisted in the current transaction (if any).
+ handle = getUnenlistedResource(resourceSpec, resourceAllocator);
+ if (handle != null) {
+ handle.getResourceState().incrementUsageCount();
+ if (poolLifeCycleListener != null) {
+ poolLifeCycleListener.connectionUsed(handle.getId());
+ poolLifeCycleListener.decrementNumConnFree();
}
}
-
- alloc.fillInResourceObjects(result);
- return result;
+ return handle;
}
- /**
- * Overridden in AssocWithThreadResourcePool to fetch the resource cached in the ThreadLocal In ConnectionPool this
- * simply returns null.
- *
- * @param spec the ResourceSpec used to locate the correct resource pool
- * @param alloc ResourceAllocator to create a resource
- * @return ResourceHandle resource from ThreadLocal
- */
- protected ResourceHandle prefetch(ResourceSpec spec, ResourceAllocator alloc) {
- return null;
+ private void waitForReconfiguration() throws RetryableUnavailableException {
+ // Add to reconfig-wait-queue
+ final Object reconfigWaitMonitor = new Object();
+ synchronized (reconfigWaitMonitor) {
+ reconfigWaitQueue.addToQueue(reconfigWaitMonitor);
+ try {
+ if (reconfigWaitTime > 0) {
+ LOG.log(TRACE, "Getting into reconfig wait queue for {0} ms", reconfigWaitTime);
+ reconfigWaitMonitor.wait(reconfigWaitTime);
+ }
+ } catch (InterruptedException e) {
+ // http thread can time out, system shutdown, etc.
+ LOG.log(TRACE, "Waiting interrupted.", e);
+ Thread.currentThread().interrupt();
+ return;
+ } finally {
+ // Wait could time out or could be notified
+ LOG.log(TRACE, "Removing wait monitor from reconfig-wait-queue: {0}", reconfigWaitMonitor);
+ reconfigWaitQueue.removeFromQueue(reconfigWaitMonitor);
+ }
+ throw new RetryableUnavailableException(
+ "Pool Reconfigured, Connection Factory can retry the lookup", POOL_RECONFIGURED_ERROR_CODE);
+ }
}
-
- protected ResourceHandle internalGetResource(ResourceSpec resourceSpec, ResourceAllocator resourceAllocator,
- Transaction transaction) throws PoolingException {
- if (!poolInitialized) {
- initPool(resourceAllocator);
+ private long checkRemainingTime(final long elapsedTime) throws PoolingException {
+ if (maxWaitTime <= 0) {
+ return 0L;
}
-
- ResourceHandle resourceHandle = getResourceFromTransaction(transaction, resourceAllocator, resourceSpec);
- if (resourceHandle != null) {
- return resourceHandle;
+ if (elapsedTime < maxWaitTime) {
+ // time has not expired, determine remaining wait time.
+ return maxWaitTime - elapsedTime;
}
-
- resourceHandle = prefetch(resourceSpec, resourceAllocator);
- if (resourceHandle != null) {
- return resourceHandle;
+ if (blocked) {
+ return 0L;
}
-
- // We didnt get a connection that is already enlisted in the current transaction (if any).
- resourceHandle = getUnenlistedResource(resourceSpec, resourceAllocator);
- if (resourceHandle != null) {
- resourceHandle.getResourceState().incrementUsageCount();
- if (poolLifeCycleListener != null) {
- poolLifeCycleListener.connectionUsed(resourceHandle.getId());
- // Decrement numConnFree
- poolLifeCycleListener.decrementNumConnFree();
- }
+ // Wait time has expired
+ if (poolLifeCycleListener != null) {
+ poolLifeCycleListener.connectionTimedOut();
}
- return resourceHandle;
+ throw new PoolingException("No available resources and wait time " + maxWaitTime + " ms expired.");
}
/**
@@ -622,18 +603,16 @@ private ResourceHandle getResourceFromTransaction(Transaction transaction, Resou
try {
javaEETransaction = (JavaEETransaction) transaction;
} catch (ClassCastException e) {
- LOG.log(SEVERE,
- "Pool: getResource : transaction is not JavaEETransaction but a " + transaction.getClass().getName(),
- e);
+ LOG.log(ERROR, "Transaction is not JavaEETransaction but a " + transaction.getClass(), e);
return null;
}
// case 1. look for free and enlisted in same tx
- final Set> set = javaEETransaction.getResources(poolInfo);
- if (set == null) {
+ final Set> resources = javaEETransaction.getResources(poolInfo);
+ if (resources == null) {
return null;
}
- final Iterator> iter = set.iterator();
+ final Iterator> iter = resources.iterator();
while (iter.hasNext()) {
ResourceHandle resourceHandle = (ResourceHandle) iter.next();
if (resourceHandle.hasConnectionErrorOccurred()) {
@@ -866,13 +845,8 @@ private ResourceHandle getUnenlistedResource(ResourceAllocator resourceAllocator
resourceFromPool = resourceHandle;
break;
}
-
- // Matching, but not shareable. To be returned to the pool.
- freeResources.add(resourceHandle);
- } else {
- // Not matching. To be returned to the pool.
- freeResources.add(resourceHandle);
}
+ freeResources.add(resourceHandle);
}
} finally {
// Return all unmatched, free resources
@@ -888,20 +862,17 @@ private ResourceHandle getUnenlistedResource(ResourceAllocator resourceAllocator
freeResources.clear();
}
- if (resourceFromPool != null) {
- // Set state to Busy
- setResourceStateToBusy(resourceFromPool);
- } else {
+ if (resourceFromPool == null) {
// Set state to Busy via resizePoolAndGetNewResource call
resourceFromPool = resizePoolAndGetNewResource(resourceAllocator);
+ } else {
+ // Set state to Busy
+ setResourceStateToBusy(resourceFromPool);
}
// Resource from the pool must be marked busy when it is returned from the pool
if (resourceHandle != null) {
makeSureResourceIsBusy(resourceHandle);
- }
-
- if (resourceHandle != null) {
// Not expecting an enlisted resource to be returned from the pool
makeSureResourceIsNotEnlisted(resourceHandle);
}
@@ -1035,7 +1006,7 @@ private int purgeResources(int quantity) {
int totalResourcesRemoved = 0;
int freeResourcesCount = dataStructure.getFreeListSize();
int resourcesCount = (freeResourcesCount >= quantity) ? quantity : freeResourcesCount;
- LOG.log(FINE, "Purging resources of size: {0}", resourcesCount);
+ LOG.log(DEBUG, "Purging resources of size: {0}", resourcesCount);
for (int i = resourcesCount - 1; i >= 0; i--) {
ResourceHandle resource = dataStructure.getResource();
@@ -1100,7 +1071,7 @@ protected ResourceHandle createSingleResource(ResourceAllocator resourceAllocato
count++;
ResourceHandle resourceHandle = resourceAllocator.createResource();
long now = System.currentTimeMillis();
- LOG.log(FINE,
+ LOG.log(DEBUG,
() -> "Time taken to create a single resource: " + resourceHandle.getResourceSpec().getResourceId()
+ " and adding to the pool: " + (now - startTime) + " ms.");
if (connectionValidationRequired || validateAtmostEveryIdleSecs) {
@@ -1179,7 +1150,7 @@ public void deleteResource(ResourceHandle resourceHandle) {
@Override
public void resourceClosed(ResourceHandle handle) throws IllegalStateException {
- LOG.log(FINE, "Resource was closed, processing handle: {0}", handle);
+ LOG.log(DEBUG, "Resource was closed, processing handle: {0}", handle);
ResourceState state = handle.getResourceState();
if (!state.isBusy()) {
@@ -1205,7 +1176,7 @@ public void resourceClosed(ResourceHandle handle) throws IllegalStateException {
}
// Note handle might already be altered by another thread before it is logged!
- LOG.log(FINE, "Resource was freed after its closure: {0}", handle);
+ LOG.log(DEBUG, "Resource was freed after its closure: {0}", handle);
}
/**
@@ -1215,8 +1186,8 @@ public void resourceClosed(ResourceHandle handle) throws IllegalStateException {
*/
protected void performMaxConnectionUsageOperation(ResourceHandle handle) {
dataStructure.removeResource(handle);
- LOG.log(INFO, "Destroying connection {0} since it has reached the maximum usage of: {1}",
- new Object[] {handle.getId(), handle.getResourceState().getUsageCount()});
+ LOG.log(DEBUG, "Destroying connection {0} since it has reached the maximum usage of: {1}", handle.getId(),
+ handle.getResourceState().getUsageCount());
if (poolLifeCycleListener != null) {
poolLifeCycleListener.decrementConnectionUsed(handle.getId());
@@ -1240,7 +1211,7 @@ protected void performMaxConnectionUsageOperation(ResourceHandle handle) {
protected void freeUnenlistedResource(ResourceHandle resourceHandle) {
// TODO: There is no validation here at all that the resourceHandle.state is already set to unenlisted
- LOG.log(FINE, "freeUnenlistedResource handle: {0}", resourceHandle);
+ LOG.log(DEBUG, "freeUnenlistedResource handle: {0}", resourceHandle);
try {
getResourceFromPoolAndFreeResourceMethodsLock.lock();
if (cleanupResource(resourceHandle)) {
@@ -1298,7 +1269,7 @@ protected boolean cleanupResource(ResourceHandle resource) {
@Override
public void resourceErrorOccurred(ResourceHandle resourceHandle) throws IllegalStateException {
- LOG.log(FINE, "Resource error occurred: {0}", resourceHandle);
+ LOG.log(DEBUG, "Resource error occurred: {0}", resourceHandle);
if (failAllConnections) {
// TODO: leakDetector is not updated and isBusy state of this resource is not updated correctly: possible bug.
// leakDetector should be updated in the doFailAllConnectionsProcessing method. The resource can be updated here.
@@ -1345,7 +1316,7 @@ public void resourceErrorOccurred(ResourceHandle resourceHandle) throws IllegalS
}
private void doFailAllConnectionsProcessing() {
- LOG.log(FINE, "doFailAllConnectionsProcessing()");
+ LOG.log(DEBUG, "doFailAllConnectionsProcessing()");
cancelResizerTask();
if (poolLifeCycleListener != null) {
poolLifeCycleListener.connectionValidationFailed(dataStructure.getResourcesSize());
@@ -1357,10 +1328,10 @@ private void doFailAllConnectionsProcessing() {
try {
createResources(allocator, steadyPoolSize);
- LOG.log(FINE, "Successfully created new resources.");
+ LOG.log(DEBUG, "Successfully created new resources.");
} catch (PoolingException pe) {
// Ignore and hope the resizer does its stuff
- LOG.log(FINE, "Could not create " + steadyPoolSize + " resources.", pe);
+ LOG.log(DEBUG, "Could not create " + steadyPoolSize + " resources.", pe);
}
scheduleResizerTask();
}
@@ -1445,10 +1416,10 @@ protected void notifyWaitingThreads() {
}
}
if (waitMonitor == null) {
- LOG.log(FINE, "Wait monitor is null");
+ LOG.log(DEBUG, "Wait monitor is null");
} else {
synchronized (waitMonitor) {
- LOG.log(FINE, "Notifying wait monitor: {0}", waitMonitor);
+ LOG.log(DEBUG, "Notifying wait monitor: {0}", waitMonitor);
waitMonitor.notifyAll();
}
}
@@ -1462,13 +1433,13 @@ private void incrementNumConnFailedValidation() {
@Override
public void emptyPool() {
- LOG.log(FINE, "Emptying pool {0}", poolInfo.getName());
+ LOG.log(DEBUG, "Emptying pool {0}", poolInfo.getName());
dataStructure.removeAll();
}
@Override
public void emptyFreeConnectionsInPool() {
- LOG.log(FINE, "Emptying free connections in the pool {0}", poolInfo.getName());
+ LOG.log(DEBUG, "Emptying free connections in the pool {0}", poolInfo.getName());
// TODO this is not completely thread safe, between getResource and removeResource
// the dataStructure can be altered by other threads
@@ -1518,7 +1489,7 @@ public long getReconfigWaitTime() {
@Override
public synchronized boolean flushConnectionPool() throws PoolingException {
- LOG.log(FINE, "Flushing Connection Pool {0}", poolInfo);
+ LOG.log(DEBUG, "Flushing Connection Pool {0}", poolInfo);
if (!poolInitialized) {
throw new PoolingException(
@@ -1529,76 +1500,71 @@ public synchronized boolean flushConnectionPool() throws PoolingException {
dataStructure.removeAll();
scheduleResizerTask();
increaseSteadyPoolSize(steadyPoolSize);
- LOG.log(FINE, "Flush Connection Pool done");
+ LOG.log(DEBUG, "Flush Connection Pool done");
return true;
}
@Override
public synchronized void reconfigurePool(ConnectorConnectionPool poolResource) throws PoolingException {
- int _idleTime = Integer.parseInt(poolResource.getIdleTimeoutInSeconds()) * 1000;
+ final int idleTimeParam = Integer.parseInt(poolResource.getIdleTimeoutInSeconds()) * 1000;
if (poolInitialized) {
- if (_idleTime != idletime && _idleTime != 0) {
- idletime = _idleTime;
+ if (idleTimeParam != this.idletime && idleTimeParam != 0) {
+ this.idletime = idleTimeParam;
scheduleResizerTask();
}
- if (_idleTime == 0) {
+ if (idleTimeParam == 0) {
// resizerTask.cancel();
cancelResizerTask();
}
}
- idletime = _idleTime;
-
- resizeQuantity = Integer.parseInt(poolResource.getPoolResizeQuantity());
-
- maxWaitTime = Integer.parseInt(poolResource.getMaxWaitTimeInMillis());
- // Make sure it's not negative.
- if (maxWaitTime < 0) {
- maxWaitTime = 0;
+ this.idletime = idleTimeParam;
+ this.resizeQuantity = Integer.parseInt(poolResource.getPoolResizeQuantity());
+ this.maxWaitTime = Integer.parseInt(poolResource.getMaxWaitTimeInMillis());
+ if (this.maxWaitTime < 0) {
+ this.maxWaitTime = 0;
}
- connectionValidationRequired = poolResource.isIsConnectionValidationRequired();
- failAllConnections = poolResource.isFailAllConnections();
+ this.connectionValidationRequired = poolResource.isIsConnectionValidationRequired();
+ this.failAllConnections = poolResource.isFailAllConnections();
setAdvancedPoolConfiguration(poolResource);
- int _maxPoolSize = Integer.parseInt(poolResource.getMaxPoolSize());
- int oldMaxPoolSize = maxPoolSize;
+ final int maxPoolSizeParam = Integer.parseInt(poolResource.getMaxPoolSize());
+ final int oldMaxPoolSize = this.maxPoolSize;
- if (_maxPoolSize < steadyPoolSize) {
+ if (maxPoolSizeParam < this.steadyPoolSize) {
// should not happen, admin must throw exception when this condition happens.
// as a precaution set max pool size to steady pool size
- maxPoolSize = steadyPoolSize;
+ this.maxPoolSize = this.steadyPoolSize;
} else {
- maxPoolSize = _maxPoolSize;
+ this.maxPoolSize = maxPoolSizeParam;
}
- if (oldMaxPoolSize != maxPoolSize) {
- dataStructure.setMaxSize(maxPoolSize);
+ if (oldMaxPoolSize != this.maxPoolSize) {
+ this.dataStructure.setMaxSize(this.maxPoolSize);
}
- int _steadyPoolSize = Integer.parseInt(poolResource.getSteadyPoolSize());
- int oldSteadyPoolSize = steadyPoolSize;
-
- if (_steadyPoolSize > maxPoolSize) {
+ final int steadyPoolSizeParam = Integer.parseInt(poolResource.getSteadyPoolSize());
+ final int oldSteadyPoolSize = this.steadyPoolSize;
+ if (steadyPoolSizeParam > this.maxPoolSize) {
// should not happen, admin must throw exception when this condition happens.
// as a precaution set steady pool size to max pool size
- steadyPoolSize = maxPoolSize;
+ this.steadyPoolSize = this.maxPoolSize;
} else {
- steadyPoolSize = _steadyPoolSize;
+ this.steadyPoolSize = steadyPoolSizeParam;
}
- if (poolInitialized) {
+ if (this.poolInitialized) {
// In this case we need to kill extra connections in the pool
- // For the case where the value is increased, we need not
- // do anything
- // num resources to kill is decided by the resources in the pool.
+ // For the case where the value is increased, we need not do anything
+ //
+ // The number resources to kill is decided by the resources in the pool.
// if we have less than current maxPoolSize resources, we need to
// kill less.
- int toKill = dataStructure.getResourcesSize() - maxPoolSize;
-
+ final int toKill = this.dataStructure.getResourcesSize() - this.maxPoolSize;
if (toKill > 0) {
killExtraResources(toKill);
}
}
- reconfigureSteadyPoolSize(oldSteadyPoolSize, _steadyPoolSize);
+ reconfigureSteadyPoolSize(oldSteadyPoolSize, steadyPoolSizeParam);
}
protected void reconfigureSteadyPoolSize(int oldSteadyPoolSize, int newSteadyPoolSize) throws PoolingException {
@@ -1684,7 +1650,7 @@ public final PoolInfo getPoolInfo() {
@Override
public synchronized void cancelResizerTask() {
- LOG.log(FINE, "Cancelling resizer task.");
+ LOG.log(DEBUG, "Cancelling resizer task.");
if (resizerTask != null) {
resizerTask.cancel();
}
@@ -1729,7 +1695,7 @@ public void setMaxPoolSize(int size) {
killExtraResources(toKill);
} catch (Exception re) {
// ignore for now
- LOG.log(FINE, "setMaxPoolSize:: killExtraResources throws exception!", re);
+ LOG.log(DEBUG, "setMaxPoolSize:: killExtraResources throws exception!", re);
}
}
}
@@ -1765,7 +1731,7 @@ public void reclaimConnection(ResourceHandle handle) {
// Entity beans when used in bean managed transaction will face an issue since connections
// are destroyed during reclaim.
// Stateful session beans will work fine.
- LOG.log(INFO,
+ LOG.log(WARNING,
"Reclaiming the leaked connection of pool [{0}] and destroying it so as to avoid both"
+ " the application that leaked the connection and any other request that can potentially acquire"
+ " the same connection from the pool end up using the connection at the same time",
diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/PoolManagerImpl.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/PoolManagerImpl.java
index af682d14fc8..efec5e8fbcc 100644
--- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/PoolManagerImpl.java
+++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/PoolManagerImpl.java
@@ -139,21 +139,19 @@ void createAndInitPool(final PoolInfo poolInfo, PoolType poolType, Hashtable,
// invoked by DataSource objects to obtain a connection
@Override
- public Object getResource(ResourceSpec resourceSpec, ResourceAllocator resourceAllocator, ClientSecurityInfo clientSecurityInfo) throws PoolingException, RetryableUnavailableException {
- if (LOG.isLoggable(FINE)) {
- LOG.log(FINE, "PoolManagerImpl.getResource START, resourceSpec=" + resourceSpec + "\nresourceAllocator="
- + resourceAllocator + "\nclientSecurityInfo=" + clientSecurityInfo);
- }
- Transaction transaction = null;
- boolean transactional = resourceAllocator.isTransactional();
-
- if (transactional) {
- transaction = getResourceManager(resourceSpec).getTransaction();
- }
-
- ResourceHandle resourceHandle = getResourceFromPool(resourceSpec, resourceAllocator, clientSecurityInfo, transaction);
- if (LOG.isLoggable(FINE)) {
- LOG.log(FINE, "PoolManagerImpl.getResource handle=" + resourceHandle + ", poolStatus" + getPoolStatus(resourceSpec.getPoolInfo()));
+ public Object getResource(ResourceSpec resourceSpec, ResourceAllocator resourceAllocator,
+ ClientSecurityInfo clientSecurityInfo) throws PoolingException, RetryableUnavailableException {
+ LOG.log(FINE, () -> "PoolManagerImpl.getResource START, resourceSpec=" + resourceSpec + "\nresourceAllocator="
+ + resourceAllocator + "\nclientSecurityInfo=" + clientSecurityInfo);
+ final Transaction transaction = resourceAllocator.isTransactional()
+ ? getResourceManager(resourceSpec).getTransaction()
+ : null;
+
+ final ResourceHandle resourceHandle = getResourceFromPool(resourceSpec, resourceAllocator, clientSecurityInfo, transaction);
+ LOG.log(FINE, () -> "PoolManagerImpl.getResource handle=" + resourceHandle + ", poolStatus"
+ + getPoolStatus(resourceSpec.getPoolInfo()));
+ if (resourceHandle == null) {
+ throw new PoolingException("No resource handle available for " + resourceSpec.getPoolInfo());
}
if (!resourceHandle.supportsLazyAssociation()) {
resourceSpec.setLazyAssociatable(false);
@@ -168,7 +166,6 @@ public Object getResource(ResourceSpec resourceSpec, ResourceAllocator resourceA
managedConnection.associateConnection(connection);
} catch (ResourceException e) {
putbackDirectToPool(resourceHandle, resourceSpec.getPoolInfo());
-
throw new PoolingException(e.getMessage(), e);
}
}
@@ -188,7 +185,7 @@ public Object getResource(ResourceSpec resourceSpec, ResourceAllocator resourceA
try {
if (resourceHandle.getResourceState().isEnlisted()) {
- LOG.log(FINE, "getResource - DO NOT ENLIST because it is already Enlisted, resource=" + resourceHandle);
+ LOG.log(FINE, "getResource - DO NOT ENLIST because it is already Enlisted, resource={0}", resourceHandle);
} else {
// The spec being used here is the spec with the updated lazy enlistment info.
@@ -211,9 +208,7 @@ public Object getResource(ResourceSpec resourceSpec, ResourceAllocator resourceA
}
Object result = resourceHandle.getUserConnection();
- if (LOG.isLoggable(FINE)) {
- LOG.log(FINE, "PoolManagerImpl.getResource END, resourceHandle=" + resourceHandle);
- }
+ LOG.log(FINE, "PoolManagerImpl.getResource END, resourceHandle={0}", resourceHandle);
return result;
}
diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/UnpooledResource.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/UnpooledResource.java
index a983d56ffa4..3b4eb119ed3 100644
--- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/UnpooledResource.java
+++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/resource/pool/UnpooledResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation
+ * Copyright (c) 2022, 2025 Contributors to the Eclipse Foundation
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
@@ -46,7 +46,7 @@ public UnpooledResource(PoolInfo poolInfo, Hashtable env) throws PoolingExceptio
}
@Override
- protected synchronized void initPool(ResourceAllocator allocator) throws PoolingException {
+ protected synchronized void ensurePoolInitialized(ResourceAllocator allocator) throws PoolingException {
if (poolInitialized) {
return;
}
diff --git a/appserver/tests/admin/ssh-cluster/pom.xml b/appserver/tests/admin/ssh-cluster/pom.xml
index 8c5f8d0b39f..5bf5112111e 100644
--- a/appserver/tests/admin/ssh-cluster/pom.xml
+++ b/appserver/tests/admin/ssh-cluster/pom.xml
@@ -51,7 +51,7 @@
org.testcontainers
- junit-jupiter
+ testcontainers-junit-jupiter
org.glassfish.main.distributions
diff --git a/appserver/tests/jdbc/pom.xml b/appserver/tests/jdbc/pom.xml
index e54bae65feb..0e8ffc67e6c 100755
--- a/appserver/tests/jdbc/pom.xml
+++ b/appserver/tests/jdbc/pom.xml
@@ -40,6 +40,12 @@
glassfish-jul-extension
test
+
+ org.glassfish.main.extras
+ glassfish-embedded-all
+ ${glassfish.version}
+ test
+
org.junit.jupiter
junit-jupiter-engine
@@ -67,11 +73,11 @@
org.testcontainers
- junit-jupiter
+ testcontainers-junit-jupiter
org.testcontainers
- postgresql
+ testcontainers-postgresql
org.jboss.shrinkwrap.resolver
@@ -162,28 +168,4 @@
provided
-
-
-
-
- maven-dependency-plugin
-
-
- prepare-gf-and-db-driver.zip
-
- copy-dependencies
-
- generate-resources
-
- ${project.build.testOutputDirectory}
- org.glassfish.main.distributions,org.postgresql
- glassfish,postgresql
- zip,jar
- true
-
-
-
-
-
-
diff --git a/appserver/tests/jdbc/src/main/java/org/glassfish/main/test/jdbc/pool/war/GlassFishUserRestEndpoint.java b/appserver/tests/jdbc/src/main/java/org/glassfish/main/test/jdbc/pool/war/GlassFishUserRestEndpoint.java
index b4cc61540d5..9935a4b3122 100644
--- a/appserver/tests/jdbc/src/main/java/org/glassfish/main/test/jdbc/pool/war/GlassFishUserRestEndpoint.java
+++ b/appserver/tests/jdbc/src/main/java/org/glassfish/main/test/jdbc/pool/war/GlassFishUserRestEndpoint.java
@@ -39,19 +39,14 @@ public class GlassFishUserRestEndpoint {
@Transactional(TxType.REQUIRES_NEW)
public void create(User user) {
em.persist(user);
-// try {
-// Thread.sleep(50L);
-// } catch (InterruptedException e) {
-// Thread.currentThread().interrupt();
-// }
}
@GET
@Path("/list")
- @Transactional(TxType.NOT_SUPPORTED)
+ @Transactional(TxType.SUPPORTS)
public List list() {
- return em.createQuery("select u from User u", User.class).setMaxResults(100).getResultList();
+ return em.createQuery("select u from User u order by u.name", User.class).setMaxResults(100).getResultList();
}
diff --git a/appserver/tests/jdbc/src/main/resources/jdbc/pool/war/persistence.xml b/appserver/tests/jdbc/src/main/resources/jdbc/pool/war/persistence.xml
index 2d82f5eba22..65a6b5c62c9 100644
--- a/appserver/tests/jdbc/src/main/resources/jdbc/pool/war/persistence.xml
+++ b/appserver/tests/jdbc/src/main/resources/jdbc/pool/war/persistence.xml
@@ -8,6 +8,8 @@
jdbc/dsPoolA
+
+
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/jta/JtaTimeoutLoggingITest.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/jta/JtaTimeoutLoggingITest.java
index 0d4ccdaf75e..cb4fcb64b1f 100644
--- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/jta/JtaTimeoutLoggingITest.java
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/jta/JtaTimeoutLoggingITest.java
@@ -35,7 +35,7 @@
import org.glassfish.main.jul.record.GlassFishLogRecord;
import org.glassfish.main.test.jdbc.jta.timeout.war.AsynchronousTimeoutingJob;
import org.glassfish.main.test.jdbc.jta.timeout.war.SlowJpaPartitioner;
-import org.glassfish.main.test.perf.util.DockerTestEnvironment;
+import org.glassfish.main.test.perf.server.DockerTestEnvironment;
import org.glassfish.tests.utils.junit.TestLoggingExtension;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
@@ -52,9 +52,6 @@
import static jakarta.transaction.Status.STATUS_MARKED_ROLLBACK;
import static org.glassfish.main.test.jdbc.jta.timeout.war.SlowJpaPartitioner.TIMEOUT_IN_SECONDS;
-import static org.glassfish.main.test.perf.util.DockerTestEnvironment.asadmin;
-import static org.glassfish.main.test.perf.util.DockerTestEnvironment.deploy;
-import static org.glassfish.main.test.perf.util.DockerTestEnvironment.undeploy;
import static org.glassfish.tests.utils.junit.matcher.WaitForExecutable.waitFor;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -102,16 +99,18 @@ public class JtaTimeoutLoggingITest {
private static final String APPNAME = "jtaTimeout";
private static boolean dockerAvailable;
+
+ private static DockerTestEnvironment environment;
private static LogCollectorHandler domainLog;
private static WebTarget wsEndpoint;
-
@BeforeAll
public static void init() throws Exception {
dockerAvailable = DockerClientFactory.instance().isDockerAvailable();
assumeTrue(dockerAvailable, "Docker is not available on this environment");
- final java.util.logging.Logger logger = DockerTestEnvironment.getDomainLogger();
+ environment = DockerTestEnvironment.getInstance();
+ final java.util.logging.Logger logger = environment.getDomainLogger();
assertNotNull(logger, "domain logger was not found");
final Filter filter = event -> {
final Predicate super String> predicate = msgPart -> {
@@ -123,9 +122,10 @@ public static void init() throws Exception {
domainLog = new LogCollectorHandler(logger, 100_000, 10);
domainLog.setFilter(filter);
- asadmin("set", "configs.config.server-config.transaction-service.timeout-in-seconds=" + TIMEOUT_IN_SECONDS);
- asadmin("restart-domain", "domain1");
- wsEndpoint = deploy(APPNAME, getArchiveToDeploy());
+ environment.asadmin("set",
+ "configs.config.server-config.transaction-service.timeout-in-seconds=" + TIMEOUT_IN_SECONDS);
+ environment.asadmin("restart-domain", "domain1");
+ wsEndpoint = environment.deploy(APPNAME, getArchiveToDeploy());
}
@@ -143,8 +143,8 @@ public static void cleanup() throws Exception {
return;
}
domainLog.close();
- undeploy(APPNAME);
- asadmin("set", "configs.config.server-config.transaction-service.timeout-in-seconds=0");
+ environment.undeploy(APPNAME);
+ environment.asadmin("set", "configs.config.server-config.transaction-service.timeout-in-seconds=0");
}
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolInjectionsIT.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolInjectionsIT.java
index 61100dd8dfe..a943b2550e7 100644
--- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolInjectionsIT.java
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolInjectionsIT.java
@@ -24,7 +24,7 @@
import org.glassfish.main.test.jdbc.pool.war.DataSourceDefinitionBean;
import org.glassfish.main.test.jdbc.pool.war.JdbcDsName;
import org.glassfish.main.test.jdbc.pool.war.RestAppConfig;
-import org.glassfish.main.test.perf.util.DockerTestEnvironment;
+import org.glassfish.main.test.perf.server.DockerTestEnvironment;
import org.glassfish.tests.utils.junit.TestLoggingExtension;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@@ -32,8 +32,6 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.DockerClientFactory;
-import static org.glassfish.main.test.perf.util.DockerTestEnvironment.deploy;
-import static org.glassfish.main.test.perf.util.DockerTestEnvironment.undeploy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -42,6 +40,7 @@
public class JdbcPoolInjectionsIT {
private static final String APPNAME = "dspools";
private static boolean dockerAvailable;
+ private static DockerTestEnvironment environment;
private static WebTarget wsEndpoint;
@@ -49,7 +48,8 @@ public class JdbcPoolInjectionsIT {
public static void init() throws Exception {
dockerAvailable = DockerClientFactory.instance().isDockerAvailable();
assumeTrue(dockerAvailable, "Docker is not available on this environment");
- wsEndpoint = deploy(APPNAME, DataSourceDefinitionBean.class, JdbcDsName.class, RestAppConfig.class);
+ environment = DockerTestEnvironment.getInstance();
+ wsEndpoint = environment.deploy(APPNAME, DataSourceDefinitionBean.class, JdbcDsName.class, RestAppConfig.class);
}
@AfterAll
@@ -57,8 +57,8 @@ public static void cleanup() throws Exception {
if (!dockerAvailable) {
return;
}
- undeploy(APPNAME);
- DockerTestEnvironment.reinitializeDatabase();
+ environment.undeploy(APPNAME);
+ environment.reinitializeDatabase();
}
@Test
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolPerformanceIT.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolPerformanceIT.java
index f22b8998a29..4d5f1fdca35 100644
--- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolPerformanceIT.java
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/jdbc/pool/JdbcPoolPerformanceIT.java
@@ -19,151 +19,137 @@
import jakarta.ws.rs.client.WebTarget;
import java.lang.System.Logger;
-import java.net.URI;
+import java.time.Duration;
import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import org.apache.commons.lang3.RandomStringUtils;
import org.glassfish.main.test.jdbc.pool.war.GlassFishUserRestEndpoint;
import org.glassfish.main.test.jdbc.pool.war.RestAppConfig;
import org.glassfish.main.test.jdbc.pool.war.User;
-import org.glassfish.main.test.perf.util.DockerTestEnvironment;
-import org.glassfish.main.test.perf.util.UserRestClient;
+import org.glassfish.main.test.perf.benchmark.Environment;
+import org.glassfish.main.test.perf.benchmark.RestBenchmark;
+import org.glassfish.main.test.perf.embedded.DockerTestEnvironmentWithEmbedded;
+import org.glassfish.main.test.perf.rest.UserRestClient;
+import org.glassfish.main.test.perf.server.DockerTestEnvironment;
import org.glassfish.tests.utils.junit.TestLoggingExtension;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.junit.jupiter.api.extension.ExtendWith;
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.annotations.Scope;
-import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.results.Result;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.runner.Runner;
-import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
import org.openjdk.jmh.runner.options.Options;
-import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.testcontainers.DockerClientFactory;
import static java.lang.Math.min;
import static java.lang.System.Logger.Level.INFO;
-import static org.glassfish.main.test.jdbc.pool.JdbcPoolPerformanceIT.RestClientProvider.SYS_PROPERTY_ENDPOINT;
-import static org.glassfish.main.test.perf.util.DockerTestEnvironment.LIMIT_JDBC;
-import static org.glassfish.main.test.perf.util.DockerTestEnvironment.asadmin;
-import static org.glassfish.main.test.perf.util.DockerTestEnvironment.asadminMonitor;
-import static org.glassfish.main.test.perf.util.DockerTestEnvironment.deploy;
-import static org.glassfish.main.test.perf.util.DockerTestEnvironment.undeploy;
-import static org.glassfish.main.test.perf.util.GlassFishContainer.LIMIT_HTTP_THREADS;
-import static org.hamcrest.CoreMatchers.allOf;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_JMH_THREADS;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_HTTP_THREADS;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_JDBC;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.lessThan;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
-import static org.openjdk.jmh.runner.options.TimeValue.seconds;
+
+/**
+ *
+ * mvn clean install -pl :jdbc-tests -Dit.test=JdbcPoolPerformanceIT -DenableHWDependentTests=true -Dit.embedded=true -Dit.disconnectDatabase=false
+ *
+ */
@EnabledIfSystemProperty(
named = "enableHWDependentTests",
matches = "true",
disabledReason = "Test depends on hardware performance")
@ExtendWith(TestLoggingExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
public class JdbcPoolPerformanceIT {
private static final Logger LOG = System.getLogger(JdbcPoolPerformanceIT.class.getName());
private static final String APPNAME = "perf";
- private static final int LIMIT_JMH_THREADS = 500;
- private static boolean dockerAvailable;
+ private static final boolean DOCKER_AVAILABLE = DockerClientFactory.instance().isDockerAvailable();
+ private static final boolean EMBEDDED = Boolean.getBoolean("it.embedded");
+ private static final boolean DISCONNECT_DB = Boolean.getBoolean("it.disconnectDatabase");
+ private static final int CONN_MAX = min(LIMIT_JMH_THREADS, min(LIMIT_POOL_HTTP_THREADS, LIMIT_POOL_JDBC));
+
+ private static Environment env;
private static WebTarget wsEndpoint;
@BeforeAll
public static void init() throws Exception {
- dockerAvailable = DockerClientFactory.instance().isDockerAvailable();
- assumeTrue(dockerAvailable, "Docker is not available on this environment");
- wsEndpoint = deploy(APPNAME, getArchiveToDeploy());
+ assumeTrue(DOCKER_AVAILABLE, "Docker is not available on this environment");
+ WebArchive war = getArchiveToDeploy();
+ env = EMBEDDED ? new DockerTestEnvironmentWithEmbedded() : new DockerTestEnvironment();
+ wsEndpoint = env.start(APPNAME, war);
}
@AfterAll
public static void cleanup() throws Exception {
- if (!dockerAvailable) {
+ if (!DOCKER_AVAILABLE) {
return;
}
- undeploy(APPNAME);
- DockerTestEnvironment.reinitializeDatabase();
+ env.stop();
}
@Test
- public void testHeavyLoad() throws Exception {
- Options options = createOptions();
-// Thread networkIssue = new Thread(() -> {
-// try {
-// Thread.sleep(10);
-// DockerTestEnvironment.disconnectDatabase(2);
-// } catch (InterruptedException e) {
-// Thread.currentThread().interrupt();
-// }
-// });
-// networkIssue.start();
+ public void createUser() throws Exception {
+ Options options = RestBenchmark.createOptions(wsEndpoint.getUri(), "createUser");
+ if (DISCONNECT_DB) {
+ env.disconnectDatabase(Duration.ofSeconds(2L), Duration.ofSeconds(5));
+ }
Collection results = new Runner(options).run();
- // Print all results - for tuning.
- asadmin("get", "--monitor", "*");
assertThat(results, hasSize(1));
- Result> primaryResult = results.iterator().next().getPrimaryResult();
- long usersCreated = new UserRestClient(wsEndpoint).count();
- int jdbcConnAcquired = asadminMonitor("server.resources.domain-pool-A.numconnacquired-count");
- int jdbcConnReleased = asadminMonitor("server.resources.domain-pool-A.numconnreleased-count");
- int jdbcConnCreated = asadminMonitor("server.resources.domain-pool-A.numconncreated-count");
- int jdbcConnCurrent = asadminMonitor("server.resources.domain-pool-A.perf.numconnused-current");
- int jdbcConnFree = asadminMonitor("server.resources.domain-pool-A.numconnfree-current");
- int jdbcMaxUsed = asadminMonitor("server.resources.domain-pool-A.perf.numconnused-highwatermark");
- int limit = min(LIMIT_JMH_THREADS, min(LIMIT_HTTP_THREADS, LIMIT_JDBC));
- double jmhThroughput = primaryResult.getScore();
- LOG.log(INFO, () -> "Results:"
- + "\nJMH throughput: " + jmhThroughput
- + "\nusersCreated: " + usersCreated
- + "\njdbcConnAcquired: " + jdbcConnAcquired
- + "\njdbcConnReleased: " + jdbcConnReleased
- + "\njdbcConnCreated: " + jdbcConnCreated
- + "\njdbcConnCurrent: " + jdbcConnCurrent
- + "\njdbcConnFree: " + jdbcConnFree
- + "\njdbcMaxUsed: " + jdbcMaxUsed
- );
+ PerformanceTestResult resultCreate = new PerformanceTestResult(results.iterator().next().getPrimaryResult());
+ LOG.log(INFO, () -> "Results(create): " + resultCreate);
+ if (!EMBEDDED) {
+ assertAll(
+ () -> assertThat("conn released==acquired", resultCreate.jdbcConnAcquired, equalTo(resultCreate.jdbcConnReleased)),
+ () -> assertThat("conn acquired", resultCreate.jdbcConnAcquired, greaterThan(CONN_MAX)),
+ () -> assertThat("conn created", resultCreate.jdbcConnCreated, equalTo(CONN_MAX)),
+ () -> assertThat("conn used now", resultCreate.jdbcConnCurrent, equalTo(0)),
+ () -> assertThat("conn usable now", resultCreate.jdbcConnFree, equalTo(CONN_MAX)),
+ () -> assertThat("conn highwatermark", resultCreate.jdbcMaxUsed, equalTo(CONN_MAX))
+ );
+ }
assertAll(
- () -> assertThat("JMH throughput", jmhThroughput, greaterThan(1_000_000d)),
- () -> assertThat("Records created", usersCreated, greaterThan(170_000L)),
- () -> assertThat("conn released==acquired", jdbcConnAcquired, equalTo(jdbcConnReleased)),
- () -> assertThat("conn acquired", jdbcConnAcquired, greaterThan(limit)),
- () -> assertThat("conn created", jdbcConnCreated, equalTo(limit)),
- () -> assertThat("conn used now", jdbcConnCurrent, equalTo(0)),
- () -> assertThat("conn usable now", jdbcConnFree, equalTo(limit)),
- () -> assertThat("conn highwatermark", jdbcMaxUsed, equalTo(limit))
+ () -> assertThat("Average Time (ms)", resultCreate.avgTime, lessThan(100d)),
+ () -> assertThat("Records created", resultCreate.usersCreated, greaterThan(150_000L))
);
}
- private Options createOptions() {
- ChainedOptionsBuilder builder = new OptionsBuilder().include(getClass().getName() + ".*");
- builder.shouldFailOnError(true);
- builder.warmupIterations(0);
- builder.timeUnit(TimeUnit.MILLISECONDS).mode(Mode.Throughput);
- builder.detectJvmArgs().jvmArgsAppend("-D" + SYS_PROPERTY_ENDPOINT + "=" + wsEndpoint.getUri());
- builder.forks(1).threads(LIMIT_JMH_THREADS);
- builder.operationsPerInvocation(1_000_000).measurementTime(seconds(5)).timeout(seconds(30));
- return builder.build();
- }
+ @Test
+ public void listUsers() throws Exception {
+ Options options = RestBenchmark.createOptions(wsEndpoint.getUri(), "listUsers");
+ if (DISCONNECT_DB) {
+ env.disconnectDatabase(Duration.ofSeconds(2L), Duration.ofSeconds(5));
+ }
- @Benchmark
- public void meanResponseTimeBenchmark(RestClientProvider clientProvider) throws Exception {
- User user = new User(RandomStringUtils.insecure().nextAlphabetic(32));
- UserRestClient client = clientProvider.getClient();
- client.create(user);
- List users = client.list();
- assertThat(users, hasSize(allOf(greaterThan(0), lessThanOrEqualTo(100))));
+ Collection results = new Runner(options).run();
+ assertThat(results, hasSize(1));
+ PerformanceTestResult resultList = new PerformanceTestResult(results.iterator().next().getPrimaryResult());
+ LOG.log(INFO, () -> "Results(list): " + resultList);
+ if (!EMBEDDED) {
+ assertAll(
+ () -> assertThat("conn released==acquired", resultList.jdbcConnAcquired, equalTo(resultList.jdbcConnReleased)),
+ () -> assertThat("conn acquired", resultList.jdbcConnAcquired, greaterThan(CONN_MAX)),
+ () -> assertThat("conn created", resultList.jdbcConnCreated, equalTo(CONN_MAX)),
+ () -> assertThat("conn used now", resultList.jdbcConnCurrent, equalTo(0)),
+ () -> assertThat("conn usable now", resultList.jdbcConnFree, equalTo(CONN_MAX)),
+ () -> assertThat("conn highwatermark", resultList.jdbcMaxUsed, equalTo(CONN_MAX))
+ );
+ }
+ assertAll(
+ () -> assertThat("Average Time (ms)", resultList.avgTime, lessThan(2000d)),
+ () -> assertThat("Records created", resultList.usersCreated, equalTo(resultList.usersCreated))
+ );
}
@@ -174,14 +160,46 @@ private static WebArchive getArchiveToDeploy() throws Exception {
;
}
- @State(Scope.Benchmark)
- public static class RestClientProvider {
- public static final String SYS_PROPERTY_ENDPOINT = "endpoint";
- private static final UserRestClient CLIENT = new UserRestClient(
- URI.create(System.getProperty(SYS_PROPERTY_ENDPOINT)), false);
+ private static class PerformanceTestResult {
+ long usersCreated;
+ int jdbcConnAcquired;
+ int jdbcConnReleased;
+ int jdbcConnCreated;
+ int jdbcConnCurrent;
+ int jdbcConnFree;
+ int jdbcMaxUsed;
+ double avgTime;
+
+ PerformanceTestResult(Result> result) {
+ usersCreated = new UserRestClient(wsEndpoint).count();
+ jdbcConnAcquired = asadminMonitor("server.resources.domain-pool-A.numconnacquired-count");
+ jdbcConnReleased = asadminMonitor("server.resources.domain-pool-A.numconnreleased-count");
+ jdbcConnCreated = asadminMonitor("server.resources.domain-pool-A.numconncreated-count");
+ jdbcConnCurrent = asadminMonitor("server.resources.domain-pool-A.perf.numconnused-current");
+ jdbcConnFree = asadminMonitor("server.resources.domain-pool-A.numconnfree-current");
+ jdbcMaxUsed = asadminMonitor("server.resources.domain-pool-A.perf.numconnused-highwatermark");
+ avgTime = result.getScore();
+ }
+
+
+ @Override
+ public String toString() {
+ return "\nAverage Time (ms): " + avgTime
+ + "\nusersCreated: " + usersCreated
+ + "\njdbcConnAcquired: " + jdbcConnAcquired
+ + "\njdbcConnReleased: " + jdbcConnReleased
+ + "\njdbcConnCreated: " + jdbcConnCreated
+ + "\njdbcConnCurrent: " + jdbcConnCurrent
+ + "\njdbcConnFree: " + jdbcConnFree
+ + "\njdbcMaxUsed: " + jdbcMaxUsed;
+ }
- public UserRestClient getClient() {
- return CLIENT;
+ private int asadminMonitor(String string) {
+ if (env instanceof DockerTestEnvironmentWithEmbedded) {
+ return 0;
+ }
+ DockerTestEnvironment gfEnv = (DockerTestEnvironment) env;
+ return gfEnv.asadminMonitor(string);
}
}
}
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/BenchmarkLimits.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/BenchmarkLimits.java
new file mode 100644
index 00000000000..4dcc0e682fd
--- /dev/null
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/BenchmarkLimits.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2025 Eclipse Foundation and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.main.test.perf.benchmark;
+
+
+/**
+ * Configuration of several critical values affecting performance and stability.
+ */
+public final class BenchmarkLimits {
+
+ public static final int LIMIT_JMH_THREADS = 500;
+ public static final int LIMIT_POOL_HTTP_THREADS = 1000;
+ // FIXME: 4 reproduces the JDBC Pool error - pool contains just unusable connections!
+ public static final int LIMIT_HTTP_REQUEST_TIMEOUT = 10;
+
+ public static final int LIMIT_DBSERVER_CONNECTION_COUNT = 500;
+ public static final int LIMIT_POOL_JDBC = 300;
+ public static final int LIMIT_POOL_EJB = 500;
+ public static final boolean ENABLE_CONNECTION_VALIDATION = false;
+
+ public static final int MEM_MAX_APP_OS = 4;
+ public static final int MEM_MAX_APP_HEAP = 3;
+ public static final int MEM_MAX_DB_OS = 4;
+ public static final int MEM_MAX_DB_SHARED = 4;
+
+ public static final String SERVER_LOG_LEVEL = "INFO";
+
+ private BenchmarkLimits() {
+ // hidden
+ }
+}
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/Environment.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/Environment.java
new file mode 100644
index 00000000000..89146fbcd23
--- /dev/null
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/Environment.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2025 Eclipse Foundation and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.main.test.perf.benchmark;
+
+import com.github.database.rider.core.api.connection.ConnectionHolder;
+import com.github.database.rider.core.api.dataset.DataSetExecutor;
+import com.github.database.rider.core.dataset.DataSetExecutorImpl;
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.model.HostConfig;
+import com.github.dockerjava.api.model.Ulimit;
+
+import jakarta.ws.rs.client.WebTarget;
+
+import java.lang.System.Logger;
+import java.time.Duration;
+
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.postgresql.ds.PGSimpleDataSource;
+import org.testcontainers.DockerClientFactory;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+import static java.lang.System.Logger.Level.INFO;
+import static java.lang.System.Logger.Level.TRACE;
+import static java.lang.System.Logger.Level.WARNING;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_DBSERVER_CONNECTION_COUNT;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_DB_OS;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_DB_SHARED;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/**
+ * The environment of the test, database server, application server, network.
+ */
+public abstract class Environment {
+ private static final Logger LOG = System.getLogger(Environment.class.getName());
+ private static final Logger LOG_DB = System.getLogger("DB");
+
+ private final Network network = Network.newNetwork();
+
+ @SuppressWarnings("resource")
+ private final PostgreSQLContainer> database = new PostgreSQLContainer<>("postgres:17.6")
+ .withNetwork(network).withDatabaseName("testdb").withExposedPorts(5432).withCreateContainerCmdModifier(cmd -> {
+ cmd.withHostName("tc-testdb");
+ cmd.withAttachStderr(true);
+ cmd.withAttachStdout(true);
+ final HostConfig hostConfig = cmd.getHostConfig();
+ hostConfig.withMemory(MEM_MAX_DB_OS * 1024 * 1024 * 1024L);
+ hostConfig.withMemorySwappiness(0L);
+ hostConfig.withUlimits(new Ulimit[] {new Ulimit("nofile", 4096L, 8192L)});
+ hostConfig.withShmSize(MEM_MAX_DB_SHARED * 1024 * 1024 * 1024L);
+ })
+ .withLogConsumer(o -> LOG_DB.log(INFO, o.getUtf8StringWithoutLineEnding()))
+ .withCommand("postgres",
+ "-c", "log_statement=none",
+ "-c", "log_destination=stderr",
+ "-c", "max_connections=" + LIMIT_DBSERVER_CONNECTION_COUNT
+ );
+
+ private ConnectionHolder connectionHolder;
+ private DataSetExecutor dsExecutor;
+
+ /** @return Docker network */
+ public Network getNetwork() {
+ return network;
+ }
+
+ /**
+ * @return database container
+ */
+ public PostgreSQLContainer> getDatabase() {
+ return database;
+ }
+
+ /**
+ * Start the environment - network and database, children may start more containers.
+ * Then deploys the application.
+ * @param appName
+ *
+ * @param war
+ * @return endpoint of the application.
+ */
+ public WebTarget start(String appName, WebArchive war) {
+ start();
+ return deploy(appName, war);
+ }
+
+ /**
+ * Start the environment - network and database
+ */
+ public void start() {
+ assumeTrue(DockerClientFactory.instance().isDockerAvailable(), "Docker is not available on this environment");
+ PostgreSQLContainer> db = getDatabase();
+ db.start();
+
+ final PGSimpleDataSource dataSource = new PGSimpleDataSource();
+ dataSource.setDatabaseName(db.getDatabaseName());
+ dataSource.setServerNames(new String[] {db.getHost()});
+ dataSource.setUrl(db.getJdbcUrl());
+ dataSource.setUser(db.getUsername());
+ dataSource.setPassword(db.getPassword());
+ dataSource.setConnectTimeout(60);
+ connectionHolder = dataSource::getConnection;
+ dsExecutor = DataSetExecutorImpl.instance("executor", connectionHolder);
+ reinitializeDatabase();
+ }
+
+ /**
+ * Stops all virtual computers, and destroys the virtual network.
+ */
+ public void stop() {
+ PostgreSQLContainer> db = getDatabase();
+ if (db.isRunning()) {
+ closeSilently(db);
+ }
+ closeSilently(getNetwork());
+ }
+
+ /**
+ * Deploys the war file under the given application name
+ *
+ * @param appName
+ * @param war
+ * @return endpoint
+ */
+ public abstract WebTarget deploy(String appName, WebArchive war);
+
+ /**
+ * Undeploys the war file registered under the given application name.
+ *
+ * @param appname
+ */
+ public abstract void undeploy(String appname);
+
+ /**
+ * Disconnects the database for the duration. Then reconnects.
+ *
+ * @param delay
+ * @param duration
+ */
+ public void disconnectDatabase(Duration delay, Duration duration) {
+ Thread networkIssue = new Thread(() -> {
+ try {
+ Thread.sleep(delay.toMillis());
+ disconnectDatabase(duration);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+ networkIssue.start();
+ }
+
+ /**
+ * Disconnects the database for the duration. Then reconnects.
+ *
+ * @param duration
+ */
+ public void disconnectDatabase(Duration duration) {
+ DockerClient client = DockerClientFactory.instance().client();
+ LOG.log(INFO, "Disconnecting database from network!");
+ client.disconnectFromNetworkCmd().withNetworkId(network.getId()).withContainerId(database.getContainerId()).exec();
+ try {
+ Thread.sleep(duration.toMillis());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ client.connectToNetworkCmd().withNetworkId(network.getId()).withContainerId(database.getContainerId()).exec();
+ LOG.log(INFO, "Database reconnected to the network!");
+ }
+
+ /**
+ * Drop all data and recreate just those existing before the test.
+ */
+ public void reinitializeDatabase() {
+ dsExecutor.executeScript("initSchema.sql");
+ }
+
+ /**
+ * Closes the {@link AutoCloseable} - if it throws exception, the exception is just logged.
+ * Useful for situation where we are closing whole environment and we don't plan any reaction
+ * to possible exceptions.
+ *
+ * @param closeable
+ */
+ protected static void closeSilently(final AutoCloseable closeable) {
+ LOG.log(TRACE, "closeSilently(closeable={0})", closeable);
+ if (closeable == null) {
+ return;
+ }
+ try {
+ closeable.close();
+ } catch (final Exception e) {
+ LOG.log(WARNING, "Close method caused an exception.", e);
+ }
+ }
+}
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/RestBenchmark.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/RestBenchmark.java
new file mode 100644
index 00000000000..983969d639f
--- /dev/null
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/benchmark/RestBenchmark.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2025 Eclipse Foundation and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.main.test.perf.benchmark;
+
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.glassfish.main.test.jdbc.pool.war.User;
+import org.glassfish.main.test.perf.rest.UserRestClient;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_JMH_THREADS;
+import static org.glassfish.main.test.perf.benchmark.RestBenchmark.RestClientProvider.SYS_PROPERTY_ENDPOINT;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.openjdk.jmh.runner.options.TimeValue.seconds;
+
+/**
+ *
+ */
+public class RestBenchmark {
+
+ public static Options createOptions(URI wsEndpoint, String benchmark) {
+ ChainedOptionsBuilder builder = new OptionsBuilder().include(RestBenchmark.class.getName() + '.' + benchmark);
+ builder.shouldFailOnError(true);
+ builder.warmupIterations(1).warmupTime(seconds(5L));
+ builder.timeUnit(TimeUnit.MILLISECONDS).mode(Mode.AverageTime);
+ builder.detectJvmArgs().jvmArgsAppend("-D" + SYS_PROPERTY_ENDPOINT + "=" + wsEndpoint);
+ builder.forks(1).threads(LIMIT_JMH_THREADS);
+ builder
+ .measurementIterations(2).measurementTime(seconds(10))
+ .timeout(seconds(10));
+ return builder.build();
+ }
+
+ // Don't forget benchmarks are executed alphabetically!
+
+ @Benchmark
+ public void createUser(RestClientProvider clientProvider) throws Exception {
+ User user = new User(RandomStringUtils.insecure().nextAlphabetic(32));
+ UserRestClient client = clientProvider.getClient();
+ client.create(user);
+ }
+
+ @Benchmark
+ public void listUsers(RestClientProvider clientProvider) throws Exception {
+ UserRestClient client = clientProvider.getClient();
+ List users = client.list();
+ assertThat(users, hasSize(equalTo(100)));
+ }
+
+ @State(Scope.Benchmark)
+ public static class RestClientProvider {
+ public static final String SYS_PROPERTY_ENDPOINT = "endpoint";
+ private static final UserRestClient CLIENT = new UserRestClient(
+ URI.create(System.getProperty(SYS_PROPERTY_ENDPOINT)), false);
+
+ public UserRestClient getClient() {
+ return CLIENT;
+ }
+ }
+}
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/DockerTestEnvironmentWithEmbedded.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/DockerTestEnvironmentWithEmbedded.java
new file mode 100644
index 00000000000..65146ab2c46
--- /dev/null
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/DockerTestEnvironmentWithEmbedded.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.main.test.perf.embedded;
+
+import jakarta.ws.rs.client.WebTarget;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.System.Logger;
+
+import org.glassfish.main.test.perf.benchmark.Environment;
+import org.jboss.shrinkwrap.api.exporter.ZipExporter;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+
+import static java.lang.System.Logger.Level.INFO;
+import static org.glassfish.main.test.perf.embedded.EmbeddedGlassFishContainer.WAR_FILE;
+import static org.testcontainers.utility.MountableFile.forHostPath;
+
+/**
+ * Environment of Eclipse GlassFish, Derby and Postgress SQL databases.
+ */
+public class DockerTestEnvironmentWithEmbedded extends Environment {
+
+ private static final Logger LOG = System.getLogger(DockerTestEnvironmentWithEmbedded.class.getName());
+
+ private final EmbeddedGlassFishContainer app;
+
+ /**
+ * Creates network, databases and application server, but doesn't start them.
+ */
+ public DockerTestEnvironmentWithEmbedded() {
+ app = new EmbeddedGlassFishContainer(getNetwork(), "admin", "A", getDatabase());
+ Thread hook = new Thread(this::stop);
+ Runtime.getRuntime().addShutdownHook(hook);
+ }
+
+
+ @Override
+ public WebTarget start(String appName, WebArchive war) {
+ super.start();
+ undeploy(appName);
+ return deploy(appName, war);
+ }
+
+ @Override
+ public WebTarget deploy(String appName, WebArchive war) {
+ app.withCopyFileToContainer(forHostPath(toFile("webapp", war).getPath()), WAR_FILE);
+ app.start();
+ return app.getRestClient("");
+ }
+
+ @Override
+ public void undeploy(String appname) {
+ if (app.isRunning()) {
+ app.stop();
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (app.isRunning()) {
+ closeSilently(app);
+ }
+ super.stop();
+ }
+
+
+ private static File toFile(final String appName, final WebArchive war) {
+ LOG.log(INFO, () -> war.toString(true));
+ File warFile;
+ try {
+ warFile = File.createTempFile(appName, ".war");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ war.as(ZipExporter.class).exportTo(warFile, true);
+ return warFile;
+ }
+}
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/EmbeddedGlassFishContainer.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/EmbeddedGlassFishContainer.java
new file mode 100644
index 00000000000..bc4a294daa9
--- /dev/null
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/embedded/EmbeddedGlassFishContainer.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2024, 2025 Contributors to the Eclipse Foundation.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.main.test.perf.embedded;
+
+import com.github.dockerjava.api.model.HostConfig;
+import com.github.dockerjava.api.model.Ulimit;
+
+import jakarta.ws.rs.client.WebTarget;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.Properties;
+import java.util.logging.Level;
+
+import org.glassfish.main.test.perf.rest.RestClientUtilities;
+import org.glassfish.tests.utils.junit.JUnitSystem;
+import org.jboss.shrinkwrap.resolver.api.maven.Maven;
+import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage;
+import org.postgresql.ds.PGSimpleDataSource;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+import static org.glassfish.main.jul.cfg.GlassFishLoggingConstants.CLASS_LOG_MANAGER_GLASSFISH;
+import static org.glassfish.main.jul.cfg.GlassFishLoggingConstants.JVM_OPT_LOGGING_CFG_DEFAULT_LEVEL;
+import static org.glassfish.main.jul.cfg.GlassFishLoggingConstants.JVM_OPT_LOGGING_CFG_USE_DEFAULTS;
+import static org.glassfish.main.jul.cfg.GlassFishLoggingConstants.JVM_OPT_LOGGING_MANAGER;
+import static org.glassfish.main.test.jdbc.pool.war.JdbcDsName.JDBC_DS_POOL_A;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.ENABLE_CONNECTION_VALIDATION;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_HTTP_REQUEST_TIMEOUT;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_EJB;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_HTTP_THREADS;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_JDBC;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_APP_HEAP;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_APP_OS;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.SERVER_LOG_LEVEL;
+import static org.testcontainers.utility.MountableFile.forHostPath;
+
+/**
+ * Simple Embedded GlassFish container
+ */
+public class EmbeddedGlassFishContainer extends GenericContainer {
+
+ private static final java.util.logging.Logger LOG_GF = java.util.logging.Logger.getLogger("EGF");
+
+ public static final String WAR_FILE = "/tmp.war";
+
+ /**
+ * Creates preconfigured container with GlassFish.
+ *
+ * @param network
+ * @param hostname
+ * @param logPrefix
+ */
+ public EmbeddedGlassFishContainer(Network network, String hostname, String logPrefix, PostgreSQLContainer> db) {
+ super("eclipse-temurin:" + Runtime.version().feature());
+ PomEquippedResolveStage resolver = Maven.resolver()
+ .loadPomFromFile(JUnitSystem.detectBasedir().resolve("pom.xml").toFile());
+ Path jarFile = resolver.resolve("org.glassfish.main.extras:glassfish-embedded-all")
+ .withoutTransitivity().asSingleFile().toPath();
+ Path jdbcDriverFile = resolver.resolve("org.postgresql:postgresql")
+ .withoutTransitivity().asSingleFile().toPath();
+ final String poolName = "domain-pool-" + JDBC_DS_POOL_A.charAt(JDBC_DS_POOL_A.length() - 1);
+ Path properties = generateProperties(db, poolName);
+ withNetwork(network)
+ .withCopyFileToContainer(forHostPath(jarFile), "/embedded-glassfish.jar")
+ .withCopyFileToContainer(forHostPath(jdbcDriverFile), "/postgresql.jar")
+ .withCopyFileToContainer(forHostPath(properties), "/gf-cfg.properties")
+ .withEnv("TZ", "UTC").withEnv("LC_ALL", "en_US.UTF-8")
+ .withStartupAttempts(1)
+ .withStartupTimeout(Duration.ofSeconds(30L))
+ .withCreateContainerCmdModifier(cmd -> {
+ cmd.withEntrypoint("/bin/sh", "-c");
+ cmd.withCmd(getCommandAdmin());
+ cmd.withHostName(hostname);
+ cmd.withAttachStderr(true);
+ cmd.withAttachStdout(true);
+ final HostConfig hostConfig = cmd.getHostConfig();
+ hostConfig.withMemory(MEM_MAX_APP_OS * 1024 * 1024 * 1024L);
+ hostConfig.withMemorySwappiness(0L);
+ hostConfig.withUlimits(new Ulimit[] {new Ulimit("nofile", 4096L, 8192L)});
+ })
+ .withLogConsumer(o -> LOG_GF.log(Level.INFO, o.getUtf8StringWithoutLineEnding()))
+ .withExposedPorts(8080)
+ .waitingFor(Wait.forLogMessage(".*########### GLASSFISH STARTED ###########.*", 1)
+ .withStartupTimeout(Duration.ofMinutes(5L)));
+ }
+
+ /**
+ * @return JUL Logger to allow attaching handlers
+ */
+ public java.util.logging.Logger getLogger() {
+ return LOG_GF;
+ }
+
+ /**
+ * @param context
+ * @return HTTP REST client following redirects, logging requests and responses
+ */
+ public WebTarget getRestClient(String context) {
+ return RestClientUtilities.getWebTarget(getHttpEndPoint(context), true);
+ }
+
+ private URI getHttpEndPoint(String context) {
+ return URI.create("http://" + getHost() + ":" + getMappedPort(8080) + "/" + context);
+ }
+
+ private static Path generateProperties(PostgreSQLContainer> db, final String poolName) {
+ try {
+ Path properties = Files.createTempFile("gf-cfg", "properties");
+ Properties cfg = new Properties();
+ cfg.setProperty("command.0.addLibrary", "add-library " + "/postgresql.jar");
+ cfg.setProperty("command.1.createPool", "create-jdbc-connection-pool"
+// + " --ping"
+ + " --restype javax.sql.DataSource"
+ + " --datasourceclassname " + PGSimpleDataSource.class.getName()
+ + " --steadypoolsize 0 --maxpoolsize " + LIMIT_POOL_JDBC
+ + " --validationmethod auto-commit"
+ + " --isconnectvalidatereq " + ENABLE_CONNECTION_VALIDATION + " --failconnection true" //
+ + " --property user=" + db.getUsername() + ":password=" + db.getPassword() //
+ + ":DatabaseName=" + db.getDatabaseName() //
+ + ":ServerName=tc-testdb:port=" + 5432 + ":connectTimeout=10 " //
+ + poolName
+ );
+ cfg.setProperty("command.2.createResource",
+ "create-jdbc-resource --connectionpoolid " + poolName + ' ' + JDBC_DS_POOL_A);
+// cfg.setProperty("command.3.get", "get *");
+
+ cfg.setProperty("configs.config.server-config.ejb-container.max-pool-size",
+ Integer.toString(LIMIT_POOL_EJB));
+ cfg.setProperty(
+ "configs.config.server-config.thread-pools.thread-pool.http-thread-pool.max-thread-pool-size",
+ Integer.toString(LIMIT_POOL_HTTP_THREADS));
+ cfg.setProperty(
+ "configs.config.server-config.monitoring-service.module-monitoring-levels.jdbc-connection-pool",
+ "HIGH");
+ cfg.setProperty(
+ "configs.config.server-config.network-config.protocols.protocol.https-listener.http.request-timeout-seconds",
+ Integer.toString(LIMIT_HTTP_REQUEST_TIMEOUT));
+
+ try (FileOutputStream outputStream = new FileOutputStream(properties.toFile())) {
+ cfg.store(outputStream, null);
+ }
+ return properties;
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static String getCommandAdmin() {
+ final StringBuilder command = new StringBuilder();
+ command.append("echo \"***************** Starting Embedded GlassFish *****************\"");
+ command.append(" && set -x && set -e");
+ command.append(" && export LANG=\"en_US.UTF-8\"").append(" && export LANGUAGE=\"en_US.UTF-8\"");
+ command.append(" && (env | sort) && locale");
+ command.append(" && ulimit -a");
+ command.append(" && cat /etc/hosts && cat /etc/resolv.conf");
+ command.append(" && hostname");
+ command.append(" && java -version");
+ command.append(" && mkdir -p /opt");
+ command.append(" && cd /opt");
+ command.append(" && cat /gf-cfg.properties");
+ command.append(" && java ")
+ .append(" -D").append(JVM_OPT_LOGGING_MANAGER).append('=').append(CLASS_LOG_MANAGER_GLASSFISH)
+ .append(" -D").append(JVM_OPT_LOGGING_CFG_USE_DEFAULTS).append('=').append(true)
+ .append(" -D").append(JVM_OPT_LOGGING_CFG_DEFAULT_LEVEL).append('=').append(SERVER_LOG_LEVEL)
+ .append(" -Xmx").append(MEM_MAX_APP_HEAP).append('g')
+ .append(" -jar /embedded-glassfish.jar --properties=").append("/gf-cfg.properties")
+ .append(" --httpPort=8080 ")
+ .append(WAR_FILE);
+ return command.toString();
+ }
+}
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/LoggingResponseFilter.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/LoggingResponseFilter.java
similarity index 86%
rename from appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/LoggingResponseFilter.java
rename to appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/LoggingResponseFilter.java
index 432d272095d..f9ae861b93a 100644
--- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/LoggingResponseFilter.java
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/LoggingResponseFilter.java
@@ -14,7 +14,7 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
-package org.glassfish.main.test.perf.util;
+package org.glassfish.main.test.perf.rest;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientResponseContext;
@@ -22,9 +22,9 @@
import java.lang.System.Logger;
-import org.testcontainers.shaded.org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
-import static java.lang.System.Logger.Level.INFO;
+import static java.lang.System.Logger.Level.DEBUG;
/**
* Logs REST communication when response comes.
@@ -36,10 +36,10 @@ public class LoggingResponseFilter implements ClientResponseFilter {
@Override
public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) {
- if (!LOG.isLoggable(INFO)) {
+ if (!LOG.isLoggable(DEBUG)) {
return;
}
- LOG.log(INFO, "filter(requestContext, responseContext);" //
+ LOG.log(DEBUG, "filter(requestContext, responseContext);" //
+ "\nrequestContext: {0}"
+ "\nrequest headers: {1}"
+ "\nrequest cookies: {2}"
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/RestClientUtilities.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/RestClientUtilities.java
similarity index 97%
rename from appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/RestClientUtilities.java
rename to appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/RestClientUtilities.java
index bcde743c306..182f20632d4 100644
--- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/RestClientUtilities.java
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/RestClientUtilities.java
@@ -14,7 +14,7 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
-package org.glassfish.main.test.perf.util;
+package org.glassfish.main.test.perf.rest;
import com.fasterxml.jackson.databind.ObjectMapper;
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/UserRestClient.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/UserRestClient.java
similarity index 98%
rename from appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/UserRestClient.java
rename to appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/UserRestClient.java
index 56004c20e05..cbc23547712 100644
--- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/UserRestClient.java
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/rest/UserRestClient.java
@@ -14,7 +14,7 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
-package org.glassfish.main.test.perf.util;
+package org.glassfish.main.test.perf.rest;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.WebTarget;
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/DockerTestEnvironment.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/DockerTestEnvironment.java
new file mode 100644
index 00000000000..e6851ac4507
--- /dev/null
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/DockerTestEnvironment.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.main.test.perf.server;
+
+import jakarta.ws.rs.client.WebTarget;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.System.Logger;
+
+import org.glassfish.main.test.perf.benchmark.Environment;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.exporter.ZipExporter;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.postgresql.ds.PGSimpleDataSource;
+import org.testcontainers.containers.Container.ExecResult;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+import static java.lang.System.Logger.Level.INFO;
+import static org.glassfish.main.test.jdbc.pool.war.JdbcDsName.JDBC_DS_POOL_A;
+import static org.glassfish.main.test.jdbc.pool.war.JdbcDsName.JDBC_DS_POOL_B;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.ENABLE_CONNECTION_VALIDATION;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_JDBC;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.stringContainsInOrder;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Environment of Eclipse GlassFish server, Derby and PostgreSQL databases.
+ */
+public class DockerTestEnvironment extends Environment {
+
+ private static final Logger LOG = System.getLogger(DockerTestEnvironment.class.getName());
+
+ private static DockerTestEnvironment instance;
+
+ private final GlassFishContainer appServer;
+
+ /**
+ * Creates network, databases and application server, but doesn't start them.
+ */
+ public DockerTestEnvironment() {
+ appServer = new GlassFishContainer(getNetwork(), "admin", "A");
+ Thread hook = new Thread(this::stop);
+ Runtime.getRuntime().addShutdownHook(hook);
+ }
+
+ /**
+ * @return Logger printing GlassFish server.log file
+ */
+ public java.util.logging.Logger getDomainLogger() {
+ return appServer.getLogger();
+ }
+
+ /**
+ * GlassFish's asadmin get -m [key] execution
+ *
+ * @param key monitoring property key
+ * @return int value
+ */
+ public int asadminMonitor(String key) {
+ return appServer.asadminGetInt(key);
+ }
+
+ /**
+ * GlassFish's asadmin script execution.
+ *
+ * @param commandName
+ * @param arguments
+ * @return result of the command
+ */
+ public ExecResult asadmin(String commandName, String... arguments) {
+ return appServer.asadmin(commandName, arguments);
+ }
+
+ /**
+ * Start the environment - start Postgress database, and GlassFish's domain1.
+ * Create JDBC pool and resources.
+ * Start Derby (comes with GlassFish)
+ */
+ @Override
+ public void start() {
+ super.start();
+ PostgreSQLContainer> db = getDatabase();
+ appServer.start();
+ for (String jndiName : new String[] {JDBC_DS_POOL_A, JDBC_DS_POOL_B}) {
+ final String poolName = "domain-pool-" + jndiName.charAt(jndiName.length() - 1);
+ assertEquals(0, appServer.asadmin("create-jdbc-connection-pool", "--ping",
+ "--restype", "javax.sql.DataSource", //
+ "--datasourceclassname", PGSimpleDataSource.class.getName(), //
+ "--steadypoolsize", "0", "--maxpoolsize", Integer.toString(LIMIT_POOL_JDBC), //
+ "--validationmethod", "auto-commit", //
+ "--isconnectvalidatereq", Boolean.toString(ENABLE_CONNECTION_VALIDATION), "--failconnection", "true", //
+ "--property", "user=" + db.getUsername() + ":password=" + db.getPassword() //
+ + ":DatabaseName=" + db.getDatabaseName() //
+ + ":ServerName=tc-testdb:port=" + 5432 + ":connectTimeout=10" //
+ , //
+ poolName).getExitCode());
+ assertEquals(0,
+ appServer.asadmin("create-jdbc-resource", "--connectionpoolid", poolName, jndiName).getExitCode());
+ }
+ assertEquals(0, appServer.asadmin("get", "resources.jdbc-connection-pool.*").getExitCode());
+ assertEquals(0, appServer.asadmin("start-database").getExitCode());
+
+ final String respListPools = appServer.asadmin("list-jdbc-connection-pools").getStdout();
+ assertThat("list-jdbc-connection-pools response", respListPools,
+ stringContainsInOrder("__TimerPool", "DerbyPool", "domain-pool-A", "domain-pool-B"));
+ }
+
+
+ /**
+ * Stops all virtual computers, and destroys the virtual network.
+ */
+ @Override
+ public void stop() {
+ LOG.log(INFO, "Closing docker containers ...");
+ if (appServer.isRunning()) {
+ appServer.asadmin("stop-domain", "--kill");
+ closeSilently(appServer);
+ }
+ super.stop();
+ }
+
+
+ /**
+ * Create and deploy the war file.
+ *
+ * @param appName name and application context
+ * @param classes classes included to the war file.
+ * @return endpoint of the application - it is expected that the context root is the application
+ * name.
+ */
+ public WebTarget deploy(String appName, Class>... classes) {
+ File warFile = getArchiveToDeploy(appName, classes);
+ return deploy(appName, warFile);
+ }
+
+ /**
+ * Create and deploy the war file.
+ *
+ * @param appName name and application context
+ * @param pkgs packages included to the war file.
+ * @return endpoint of the application - it is expected that the context root is the application
+ * name.
+ */
+ public WebTarget deploy(String appName, Package... pkgs) {
+ File warFile = getArchiveToDeploy(appName, pkgs);
+ return deploy(appName, warFile);
+ }
+
+ /**
+ * Deploy the war file.
+ *
+ * @param appName name and application context
+ * @param war the war file to be deployed.
+ * @return endpoint of the application - it is expected that the context root is the application
+ * name.
+ */
+ @Override
+ public WebTarget deploy(String appName, WebArchive war) {
+ File warFile = toFile(appName, war);
+ return deploy(appName, warFile);
+ }
+
+
+ /**
+ * Deploy the war file.
+ *
+ * @param appName name and application context
+ * @param warFile the war file to be deployed.
+ * @return endpoint of the application - it is expected that the context root is the application
+ * name.
+ */
+ public WebTarget deploy(String appName, File warFile) {
+ return appServer.deploy(appName, warFile);
+ }
+
+ /**
+ * Undeploy the web application
+ *
+ * @param appName
+ */
+ @Override
+ public void undeploy(String appName) {
+ final ExecResult result = appServer.asadmin("undeploy", appName);
+ assertEquals(0, result.getExitCode(), "Undeploy exit code");
+ }
+
+ /**
+ * Creates the environment once, then provides always the same instance.
+ *
+ * @return {@link DockerTestEnvironment}
+ */
+ public static synchronized DockerTestEnvironment getInstance() {
+ if (instance == null) {
+ instance = new DockerTestEnvironment();
+ instance.start();
+ }
+ return instance;
+ }
+
+
+ private static File getArchiveToDeploy(String appName, Class>... classes) {
+ final WebArchive war = ShrinkWrap.create(WebArchive.class).addClasses(classes);
+ LOG.log(INFO, war.toString(true));
+ return toFile(appName, war);
+ }
+
+ private static File getArchiveToDeploy(String appName, Package... pkg) {
+ final WebArchive war = ShrinkWrap.create(WebArchive.class).addPackages(true, pkg);
+ return toFile(appName, war);
+ }
+
+ private static File toFile(final String appName, final WebArchive war) {
+ LOG.log(INFO, () -> war.toString(true));
+ File warFile;
+ try {
+ warFile = File.createTempFile(appName, ".war");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ war.as(ZipExporter.class).exportTo(warFile, true);
+ return warFile;
+ }
+}
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/GlassFishContainer.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/GlassFishContainer.java
similarity index 66%
rename from appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/GlassFishContainer.java
rename to appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/GlassFishContainer.java
index f9e42aa649c..defbc2776b4 100644
--- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/GlassFishContainer.java
+++ b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/server/GlassFishContainer.java
@@ -14,13 +14,14 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
-package org.glassfish.main.test.perf.util;
+package org.glassfish.main.test.perf.server;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.Ulimit;
import jakarta.ws.rs.client.WebTarget;
+import java.io.File;
import java.io.IOException;
import java.lang.System.Logger;
import java.net.URI;
@@ -30,22 +31,31 @@
import java.util.List;
import java.util.logging.Level;
+import org.glassfish.main.test.perf.rest.RestClientUtilities;
+import org.glassfish.tests.utils.junit.JUnitSystem;
+import org.jboss.shrinkwrap.resolver.api.maven.Maven;
+import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
-import org.testcontainers.utility.MountableFile;
import static java.lang.System.Logger.Level.ERROR;
import static java.lang.System.Logger.Level.INFO;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_HTTP_REQUEST_TIMEOUT;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_EJB;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.LIMIT_POOL_HTTP_THREADS;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_APP_HEAP;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.MEM_MAX_APP_OS;
+import static org.glassfish.main.test.perf.benchmark.BenchmarkLimits.SERVER_LOG_LEVEL;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.stringContainsInOrder;
+import static org.testcontainers.utility.MountableFile.forHostPath;
/**
* Simple GlassFish container based on domain1.
*/
public class GlassFishContainer extends GenericContainer {
- public static final int LIMIT_HTTP_THREADS = 1000;
- private static final int LIMIT_HTTP_REQUEST_TIMEOUT = 5;
-
private static final Logger LOG = System.getLogger(GlassFishContainer.class.getName());
private static final java.util.logging.Logger LOG_GF = java.util.logging.Logger.getLogger("GF");
@@ -61,15 +71,21 @@ public class GlassFishContainer extends GenericContainer {
/**
* Creates preconfigured container with GlassFish.
*
- * @param glassFishZip GlassFish zip file.
* @param network
* @param hostname
* @param logPrefix
*/
- public GlassFishContainer(MountableFile glassFishZip, Network network, String hostname, String logPrefix) {
+ public GlassFishContainer(Network network, String hostname, String logPrefix) {
super("eclipse-temurin:" + Runtime.version().feature());
+ PomEquippedResolveStage resolver = Maven.resolver()
+ .loadPomFromFile(JUnitSystem.detectBasedir().resolve("pom.xml").toFile());
+ Path zipFile = resolver.resolve("org.glassfish.main.distributions:glassfish:zip:?")
+ .withoutTransitivity().asSingleFile().toPath();
+ Path jdbcDriverFile = resolver.resolve("org.postgresql:postgresql:jar:?")
+ .withoutTransitivity().asSingleFile().toPath();
withNetwork(network)
- .withCopyFileToContainer(glassFishZip, "/glassfish.zip")
+ .withCopyFileToContainer(forHostPath(zipFile), "/glassfish.zip")
+ .withCopyFileToContainer(forHostPath(jdbcDriverFile), "/postgresql.jar")
.withEnv("TZ", "UTC").withEnv("LC_ALL", "en_US.UTF-8")
.withStartupAttempts(1)
.withStartupTimeout(Duration.ofSeconds(30L))
@@ -80,7 +96,7 @@ public GlassFishContainer(MountableFile glassFishZip, Network network, String ho
cmd.withAttachStderr(true);
cmd.withAttachStdout(true);
final HostConfig hostConfig = cmd.getHostConfig();
- hostConfig.withMemory(8 * 1024 * 1024 * 1024L);
+ hostConfig.withMemory(MEM_MAX_APP_OS * 1024 * 1024 * 1024L);
hostConfig.withMemorySwappiness(0L);
hostConfig.withUlimits(new Ulimit[] {new Ulimit("nofile", 4096L, 8192L)});
})
@@ -90,26 +106,47 @@ public GlassFishContainer(MountableFile glassFishZip, Network network, String ho
Wait.forLogMessage(".*Total startup time including CLI.*", 1).withStartupTimeout(Duration.ofMinutes(5L)));
}
+
/**
- * Mount provided JDBC drivers and install them to domain1 when starting the container.
- *
- * @param jdbcDriverJars
- * @return this
+ * @return JUL Logger to allow attaching handlers
*/
- public GlassFishContainer withJdbcDrivers(MountableFile... jdbcDriverJars) {
- for (MountableFile jdbcDriverFile : jdbcDriverJars) {
- withCopyFileToContainer(jdbcDriverFile,
- "/" + Path.of(jdbcDriverFile.getFilesystemPath()).getFileName().toString());
- }
- return this;
+ public java.util.logging.Logger getLogger() {
+ return LOG_GF;
}
/**
- * @return JUL Logger to allow attaching handlers
+ * Deploy the war file.
+ *
+ * @param appName name and application context
+ * @param warFile the war file to be deployed.
+ * @return endpoint of the application - it is expected that the context root is the application
+ * name.
*/
- public java.util.logging.Logger getLogger() {
- return LOG_GF;
+ public WebTarget deploy(String appName, File warFile) {
+ final String warFileInContainer = "/tmp.war";
+ copyFileToContainer(forHostPath(warFile.toPath()), warFileInContainer);
+ try {
+ final ExecResult result = asadmin("deploy", "--contextroot", appName, "--name", appName,
+ warFileInContainer);
+ assertThat("deploy response", result.getStdout(),
+ stringContainsInOrder("Application deployed with name " + appName));
+ return getRestClient(appName);
+ } finally {
+ try {
+ execInContainer("rm", warFileInContainer);
+ } catch (UnsupportedOperationException | IOException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+
+ @Override
+ public void stop() {
+ // Print all results - for tuning.
+ asadmin("get", "--monitor", "*");
+ super.stop();
}
@@ -179,17 +216,28 @@ private static String getCommandAdmin() {
command.append(" && cd ../glassfish/bin && chmod +x asadmin appclient startserv stopserv");
command.append(" && mv /*.jar ").append(PATH_DOCKER_GF_DOMAIN.resolve("lib"));
command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" start-domain ").append("domain1");
- command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set-log-levels ").append("org.postgresql.level=FINEST");
- command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append("configs.config.server-config.thread-pools.thread-pool.http-thread-pool.max-thread-pool-size=" + LIMIT_HTTP_THREADS);
- command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append("configs.config.server-config.ejb-container.max-pool-size=900");
+ // FIXME: We would have to go over all loggers
+ command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set-log-levels ").append("org.postgresql.level=").append(SERVER_LOG_LEVEL);
+ command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set-log-levels ")
+ .append("org.glassfish.main.jul.handler.GlassFishLogHandler.level=").append(SERVER_LOG_LEVEL);
+ command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ")
+ .append("configs.config.server-config.ejb-container.max-pool-size=").append(LIMIT_POOL_EJB);
+ command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ")
+ .append("configs.config.server-config.thread-pools.thread-pool.http-thread-pool.max-thread-pool-size=")
+ .append(LIMIT_POOL_HTTP_THREADS);
// Monitoring takes around 10% of throughput
- command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append("configs.config.server-config.monitoring-service.module-monitoring-levels.jdbc-connection-pool=HIGH");
+ command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append(
+ "configs.config.server-config.monitoring-service.module-monitoring-levels.jdbc-connection-pool=HIGH");
+ command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append(
+ "configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.request-timeout-seconds=")
+ .append(LIMIT_HTTP_REQUEST_TIMEOUT);
// Does thread dumps periodically, takes much more
// command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append("configs.config.server-config.monitoring-service.module-monitoring-levels.jvm=HIGH");
// FIXME: Does not work, see https://github.com/eclipse-ee4j/glassfish/issues/25701
// command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" set ").append("configs.config.server-config.monitoring-service.module-monitoring-levels.thread-pool=HIGH");
command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" delete-jvm-options ").append("-Xmx512m");
- command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" create-jvm-options ").append("-Xmx2g");
+ command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" create-jvm-options ").append("-Xmx")
+ .append(MEM_MAX_APP_HEAP).append('g');
command.append(" && ").append(PATH_DOCKER_ASADMIN).append(" restart-domain ").append("domain1");
command.append(" && tail -n 10000 -F ").append(PATH_DOCKER_GF_DOMAIN1_SERVER_LOG);
return command.toString();
diff --git a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/DockerTestEnvironment.java b/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/DockerTestEnvironment.java
deleted file mode 100644
index 1141508d4fc..00000000000
--- a/appserver/tests/jdbc/src/test/java/org/glassfish/main/test/perf/util/DockerTestEnvironment.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (c) 2025 Contributors to the Eclipse Foundation.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v. 2.0, which is available at
- * http://www.eclipse.org/legal/epl-2.0.
- *
- * This Source Code may also be made available under the following Secondary
- * Licenses when the conditions for such availability set forth in the
- * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
- * version 2 with the GNU Classpath Exception, which is available at
- * https://www.gnu.org/software/classpath/license.html.
- *
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
- */
-
-package org.glassfish.main.test.perf.util;
-
-import com.github.database.rider.core.api.connection.ConnectionHolder;
-import com.github.database.rider.core.api.dataset.DataSetExecutor;
-import com.github.database.rider.core.dataset.DataSetExecutorImpl;
-import com.github.dockerjava.api.DockerClient;
-
-import jakarta.ws.rs.client.WebTarget;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.System.Logger;
-
-import org.jboss.shrinkwrap.api.ShrinkWrap;
-import org.jboss.shrinkwrap.api.exporter.ZipExporter;
-import org.jboss.shrinkwrap.api.spec.WebArchive;
-import org.postgresql.ds.PGSimpleDataSource;
-import org.testcontainers.DockerClientFactory;
-import org.testcontainers.containers.Container.ExecResult;
-import org.testcontainers.containers.Network;
-import org.testcontainers.containers.PostgreSQLContainer;
-import org.testcontainers.utility.MountableFile;
-
-import static java.lang.System.Logger.Level.INFO;
-import static java.lang.System.Logger.Level.TRACE;
-import static java.lang.System.Logger.Level.WARNING;
-import static org.glassfish.main.test.jdbc.pool.war.JdbcDsName.JDBC_DS_POOL_A;
-import static org.glassfish.main.test.jdbc.pool.war.JdbcDsName.JDBC_DS_POOL_B;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.stringContainsInOrder;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assumptions.assumeTrue;
-import static org.testcontainers.utility.MountableFile.forClasspathResource;
-
-/**
- * Environment of Eclipse GlassFish, Derby and Postgress SQL databases.
- */
-public class DockerTestEnvironment {
- public static final int LIMIT_DB = 300;
- public static final int LIMIT_JDBC = 200;
-
- private static final Logger LOG = System.getLogger(DockerTestEnvironment.class.getName());
- private static final Logger LOG_DB = System.getLogger("DB");
-
- /** Docker network */
- private static final Network NET = Network.newNetwork();
-
- @SuppressWarnings("resource")
- private static final PostgreSQLContainer> DATABASE = new PostgreSQLContainer<>("postgres:17.6")
- .withNetwork(NET).withDatabaseName("testdb").withExposedPorts(5432).withCreateContainerCmdModifier(cmd -> {
- cmd.withHostName("tc-testdb");
- cmd.withAttachStderr(true);
- cmd.withAttachStdout(true);
- })
- .withLogConsumer(o -> LOG_DB.log(INFO, o.getUtf8StringWithoutLineEnding()))
- .withCommand("postgres",
- "-c", "log_statement=none",
- "-c", "log_destination=stderr",
- "-c", "max_connections=" + LIMIT_DB
- );
-
- @SuppressWarnings("resource")
- private static final GlassFishContainer AS_DOMAIN = new GlassFishContainer(forClasspathResource("/glassfish.zip"),
- NET, "admin", "A").withJdbcDrivers(forClasspathResource("/postgresql.jar"));
-
- static {
- Thread hook = new Thread(DockerTestEnvironment::stop);
- Runtime.getRuntime().addShutdownHook(hook);
- start();
- }
-
- private static ConnectionHolder connectionHolder;
- private static DataSetExecutor dsExecutor;
-
- /**
- * @return Logger printing GlassFish server.log file
- */
- public static java.util.logging.Logger getDomainLogger() {
- return AS_DOMAIN.getLogger();
- }
-
- /**
- * GlassFish's asadmin get -m [key] execution
- *
- * @param key monitoring property key
- * @return int value
- */
- public static int asadminMonitor(String key) {
- return AS_DOMAIN.asadminGetInt(key);
- }
-
- /**
- * GlassFish's asadmin script execution.
- *
- * @param commandName
- * @param arguments
- * @return result of the command
- */
- public static ExecResult asadmin(String commandName, String... arguments) {
- return AS_DOMAIN.asadmin(commandName, arguments);
- }
-
- /**
- * Start the environment - start Postgress database, and GlassFish's domain1.
- * Create JDBC pool and resources.
- * Start Derby (comes with GlassFish)
- */
- public static void start() {
- assumeTrue(DockerClientFactory.instance().isDockerAvailable(), "Docker is not available on this environment");
- DATABASE.start();
- AS_DOMAIN.start();
- final PGSimpleDataSource dataSource = new PGSimpleDataSource();
- dataSource.setDatabaseName(DATABASE.getDatabaseName());
- dataSource.setServerNames(new String[] {DATABASE.getHost()});
- dataSource.setUrl(DATABASE.getJdbcUrl());
- dataSource.setUser(DATABASE.getUsername());
- dataSource.setPassword(DATABASE.getPassword());
- dataSource.setConnectTimeout(60);
- connectionHolder = dataSource::getConnection;
- dsExecutor = DataSetExecutorImpl.instance("executor", connectionHolder);
- reinitializeDatabase();
-
- for (String jndiName : new String[] {JDBC_DS_POOL_A, JDBC_DS_POOL_B}) {
- final String poolName = "domain-pool-" + jndiName.charAt(jndiName.length() - 1);
- assertEquals(0, AS_DOMAIN.asadmin("create-jdbc-connection-pool", "--ping",
- "--restype", "javax.sql.DataSource", //
- "--datasourceclassname", PGSimpleDataSource.class.getName(), //
- "--steadypoolsize", "0", "--maxpoolsize", Integer.toString(LIMIT_JDBC), //
- "--validationmethod", "auto-commit", //
-// "--isconnectvalidatereq", "false", "--failconnection", "false", //
- "--isconnectvalidatereq", "true", "--failconnection", "true", //
- "--property", "user=" + DATABASE.getUsername() + ":password=" + DATABASE.getPassword() //
- + ":DatabaseName=" + DATABASE.getDatabaseName() //
- + ":ServerName=tc-testdb:port=" + 5432 + ":connectTimeout=10" //
- , //
- poolName).getExitCode());
- assertEquals(0,
- AS_DOMAIN.asadmin("create-jdbc-resource", "--connectionpoolid", poolName, jndiName).getExitCode());
- }
-
- assertEquals(0, AS_DOMAIN.asadmin("start-database").getExitCode());
-
- final String respListPools = AS_DOMAIN.asadmin("list-jdbc-connection-pools").getStdout();
- assertThat("list-jdbc-connection-pools response", respListPools,
- stringContainsInOrder("__TimerPool", "DerbyPool", "domain-pool-A", "domain-pool-B"));
- }
-
-
- public static void disconnectDatabase(int seconds) {
- DockerClient client = DockerClientFactory.instance().client();
- LOG.log(INFO, "Disconnecting database from network!");
- client.disconnectFromNetworkCmd().withNetworkId(NET.getId()).withContainerId(DATABASE.getContainerId()).exec();
- try {
- Thread.sleep(seconds * 1000L);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- client.connectToNetworkCmd().withNetworkId(NET.getId()).withContainerId(DATABASE.getContainerId()).exec();
- LOG.log(INFO, "Database reconnected to the network!");
- }
-
-
- /**
- * Stops all virtual computers, and destroys the virtual network.
- */
- public static void stop() {
- LOG.log(INFO, "Closing docker containers ...");
- if (AS_DOMAIN.isRunning()) {
- AS_DOMAIN.asadmin("stop-domain", "--kill");
- closeSilently(AS_DOMAIN);
- }
- if (DATABASE.isRunning()) {
- closeSilently(DATABASE);
- }
- closeSilently(NET);
- }
-
-
- /**
- * Drop all data and recreate just those existing before the test.
- */
- public static void reinitializeDatabase() {
- dsExecutor.executeScript("initSchema.sql");
- }
-
- /**
- * Create and deploy the war file.
- *
- * @param appName name and application context
- * @param classes classes included to the war file.
- * @return endpoint of the application - it is expected that the context root is the application
- * name.
- */
- public static WebTarget deploy(String appName, Class>... classes) {
- File warFile = getArchiveToDeploy(appName, classes);
- return deploy(appName, warFile);
- }
-
- /**
- * Create and deploy the war file.
- *
- * @param appName name and application context
- * @param pkgs packages included to the war file.
- * @return endpoint of the application - it is expected that the context root is the application
- * name.
- */
- public static WebTarget deploy(String appName, Package... pkgs) {
- File warFile = getArchiveToDeploy(appName, pkgs);
- return deploy(appName, warFile);
- }
-
- /**
- * Deploy the war file.
- *
- * @param appName name and application context
- * @param war the war file to be deployed.
- * @return endpoint of the application - it is expected that the context root is the application
- * name.
- */
- public static WebTarget deploy(String appName, WebArchive war) {
- File warFile = toFile(appName, war);
- return deploy(appName, warFile);
- }
-
- /**
- * Deploy the war file.
- *
- * @param appName name and application context
- * @param warFile the war file to be deployed.
- * @return endpoint of the application - it is expected that the context root is the application
- * name.
- */
- public static WebTarget deploy(String appName, File warFile) {
- final String warFileInContainer = "/tmp.war";
- AS_DOMAIN.copyFileToContainer(MountableFile.forHostPath(warFile.toPath()), warFileInContainer);
- try {
- final ExecResult result = AS_DOMAIN.asadmin("deploy", "--contextroot", appName, "--name", appName,
- warFileInContainer);
- assertThat("deploy response", result.getStdout(),
- stringContainsInOrder("Application deployed with name " + appName));
- return AS_DOMAIN.getRestClient(appName);
- } finally {
- try {
- AS_DOMAIN.execInContainer("rm", warFileInContainer);
- } catch (UnsupportedOperationException | IOException | InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- /**
- * Undeploy the web application
- *
- * @param appName
- */
- public static void undeploy(String appName) {
- final ExecResult result = AS_DOMAIN.asadmin("undeploy", appName);
- assertEquals(0, result.getExitCode(), "Undeploy exit code");
- }
-
- private static File getArchiveToDeploy(String appName, Class>... classes) {
- final WebArchive war = ShrinkWrap.create(WebArchive.class).addClasses(classes);
- LOG.log(INFO, war.toString(true));
- return toFile(appName, war);
- }
-
- private static File getArchiveToDeploy(String appName, Package... pkg) {
- final WebArchive war = ShrinkWrap.create(WebArchive.class).addPackages(true, pkg);
- return toFile(appName, war);
- }
-
- private static File toFile(final String appName, final WebArchive war) {
- LOG.log(INFO, () -> war.toString(true));
- File warFile;
- try {
- warFile = File.createTempFile(appName, ".war");
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- war.as(ZipExporter.class).exportTo(warFile, true);
- return warFile;
- }
-
-
- // TODO: Move to jdke
- private static void closeSilently(final AutoCloseable closeable) {
- LOG.log(TRACE, "closeSilently(closeable={0})", closeable);
- if (closeable == null) {
- return;
- }
- try {
- closeable.close();
- } catch (final Exception e) {
- LOG.log(WARNING, "Close method caused an exception.", e);
- }
- }
-}
diff --git a/appserver/tests/jdbc/src/test/resources/initSchema.sql b/appserver/tests/jdbc/src/test/resources/initSchema.sql
index a78807c30f5..d1391fc0305 100644
--- a/appserver/tests/jdbc/src/test/resources/initSchema.sql
+++ b/appserver/tests/jdbc/src/test/resources/initSchema.sql
@@ -18,6 +18,8 @@ CREATE TABLE IF NOT EXISTS "GlassFishUser" (
PRIMARY KEY (id)
);
+CREATE INDEX IF NOT EXISTS "GlassFishUserName" ON "GlassFishUser" USING BTREE ("name");
+
DELETE FROM "JEEVersion" WHERE 1=1;
DELETE FROM "Brand" WHERE 1=1;
DELETE FROm "GlassFishUser" WHERE 1=1;
diff --git a/appserver/tests/pom.xml b/appserver/tests/pom.xml
index ecd15fa626f..7e3eac44d1a 100755
--- a/appserver/tests/pom.xml
+++ b/appserver/tests/pom.xml
@@ -35,7 +35,7 @@
GlassFish Tests
- 1.21.3
+ 2.0.1
@@ -48,13 +48,13 @@
org.testcontainers
- junit-jupiter
+ testcontainers-junit-jupiter
${testcontainers.version}
test
org.testcontainers
- postgresql
+ testcontainers-postgresql
${testcontainers.version}
test
diff --git a/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/cfg/ServerFiles.java b/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/cfg/ServerFiles.java
index ee80eefdc6b..db43df9cec9 100644
--- a/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/cfg/ServerFiles.java
+++ b/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/cfg/ServerFiles.java
@@ -95,7 +95,15 @@ public static File detectInstallRoot() {
// glassfish/lib/bootstrap/glassfish.jar
File bootstrapFile = findBootstrapFile();
// glassfish/
- return bootstrapFile.getParentFile().getParentFile().getParentFile();
+ // Defensive code for embedded.
+ File parent = bootstrapFile.getParentFile();
+ for (int i = 0; i < 2; i++) {
+ if (parent == null) {
+ return null;
+ }
+ parent = parent.getParentFile();
+ }
+ return parent;
}
diff --git a/nucleus/core/kernel/src/main/java/org/glassfish/runnablejar/commandline/Option.java b/nucleus/core/kernel/src/main/java/org/glassfish/runnablejar/commandline/Option.java
index cc5ee94d060..f09742853c1 100644
--- a/nucleus/core/kernel/src/main/java/org/glassfish/runnablejar/commandline/Option.java
+++ b/nucleus/core/kernel/src/main/java/org/glassfish/runnablejar/commandline/Option.java
@@ -63,6 +63,7 @@ public enum Option {
+ " The property \"properties\" can also be defined in this file, pointing to another file."
+ " In that case, properties will be loaded also from that file.") {
+ @Override
public void handle(String value, Arguments arguments) {
loadPropertiesFromFile(value, arguments);
}
@@ -133,7 +134,7 @@ public void handle(String fileName, Arguments arguments) {
LogManager.getLogManager().readConfiguration(logConfig);
}
} catch (IOException | NullPointerException ex) {
- logger.log(WARNING, ex, () -> "Could not open properties file " + fileName);
+ LOG.log(WARNING, ex, () -> "Could not open properties file " + fileName);
}
}
},
@@ -195,21 +196,21 @@ public boolean handlesValue(String value) {
};
- protected static final Logger logger = Logger.getLogger(Option.class.getName());
+ private static final Logger LOG = Logger.getLogger(Option.class.getName());
private String mainName;
private Set aliases;
private String helpText;
private String usage;
- private Option(String mainName, Set aliases, String usage, String helpText) {
+ Option(String mainName, Set aliases, String usage, String helpText) {
this.mainName = mainName;
this.aliases = aliases;
this.usage = usage;
this.helpText = helpText;
}
- private Option(String mainName, String usage, String helpText) {
+ Option(String mainName, String usage, String helpText) {
this(mainName, Set.of(), usage, helpText);
}
@@ -286,11 +287,11 @@ protected void loadPropertiesFromFile(String fileName, Arguments arguments) {
try {
arguments.setOption(key, value);
} catch (UnknownPropertyException e) {
- logger.log(Level.WARNING, e, () -> "Invalid property '" + e.getKey() + "' in file " + fileName);
+ LOG.log(Level.WARNING, e, () -> "Invalid property '" + e.getKey() + "' in file " + fileName);
}
});
} catch (IOException e) {
- logger.log(Level.WARNING, e, () -> "Could not read properties from file " + fileName + " - " + e.getMessage());
+ LOG.log(Level.WARNING, e, () -> "Could not read properties from file " + fileName + " - " + e.getMessage());
}
}
@@ -301,7 +302,7 @@ protected void loadApplicationsFromDirectory(String directoryName, Arguments arg
arguments.deployables.add(appFile.getAbsolutePath());
}
} else {
- logger.log(WARNING, () -> "The path specified with the " + this.getUsage() + " option is not a directory: " + directoryName);
+ LOG.log(WARNING, () -> "The path specified with the " + this.getUsage() + " option is not a directory: " + directoryName);
}
}