From b58b3e6f9f988f9fa967c76078b34c783292e7d2 Mon Sep 17 00:00:00 2001 From: iitsoftware Date: Tue, 8 Apr 2025 15:19:54 +0200 Subject: [PATCH 1/8] Add support for virtual threads with IVMTaskScheduler Introduce `IVMTaskScheduler` to enable virtual thread scheduling, improving thread management when `swiftmq.jac.active` is enabled. Updated `DefaultPoolManager` to initialize thread pools with `IVMTaskScheduler` when the JAC mode is active. --- .../client/thread/DefaultPoolManager.java | 11 ++ .../client/thread/IVMTaskScheduler.java | 106 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 src/main/java/com/swiftmq/client/thread/IVMTaskScheduler.java diff --git a/src/main/java/com/swiftmq/client/thread/DefaultPoolManager.java b/src/main/java/com/swiftmq/client/thread/DefaultPoolManager.java index feee9ef..5594db5 100644 --- a/src/main/java/com/swiftmq/client/thread/DefaultPoolManager.java +++ b/src/main/java/com/swiftmq/client/thread/DefaultPoolManager.java @@ -23,6 +23,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; public class DefaultPoolManager extends PoolManager { + public static final String PROP_JAC_ACTIVE = "swiftmq.jac.active"; public static final String PROP_CONNECTOR_POOL_MIN_THREADS = "swiftmq.pool.connector.threads.min"; public static final String PROP_CONNECTOR_POOL_MAX_THREADS = "swiftmq.pool.connector.threads.max"; public static final String PROP_CONNECTOR_POOL_PRIO = "swiftmq.pool.connector.priority"; @@ -45,6 +46,16 @@ public class DefaultPoolManager extends PoolManager { ThreadPool connectionPool = null; ThreadPool sessionPool = null; ThreadPool connectorPool = null; + boolean JAC_ACTIVE = System.getProperty(PROP_JAC_ACTIVE, "false").equals("true"); + + public DefaultPoolManager() { + if (JAC_ACTIVE) { + ThreadPool pool = new IVMTaskScheduler(); + connectionPool = pool; + sessionPool = pool; + connectorPool = pool; + } + } ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); diff --git a/src/main/java/com/swiftmq/client/thread/IVMTaskScheduler.java b/src/main/java/com/swiftmq/client/thread/IVMTaskScheduler.java new file mode 100644 index 0000000..fd31d6b --- /dev/null +++ b/src/main/java/com/swiftmq/client/thread/IVMTaskScheduler.java @@ -0,0 +1,106 @@ +package com.swiftmq.client.thread; + +import com.swiftmq.swiftlet.SwiftletManager; +import com.swiftmq.swiftlet.threadpool.AsyncTask; +import com.swiftmq.swiftlet.threadpool.ThreadPool; +import com.swiftmq.swiftlet.threadpool.ThreadpoolSwiftlet; +import com.swiftmq.swiftlet.threadpool.event.FreezeCompletionListener; + +public class IVMTaskScheduler implements ThreadPool { + + ThreadpoolSwiftlet threadpoolSwiftlet = null; + + public IVMTaskScheduler() { + threadpoolSwiftlet = (ThreadpoolSwiftlet) SwiftletManager.getInstance().getSwiftlet("sys$threadpool"); + if (threadpoolSwiftlet == null) + System.err.println("[" + this + "] Failed to get ThreadpoolSwiftlet"); + else + System.out.println("[" + this + "] App uses virtual threads, scheduled in: adhocvirtual"); + } + + /** + * Closes the pool. + * Internal use only. + */ + @Override + public void close() { + + } + + /** + * Returns the pool name. + * + * @return pool name. + */ + @Override + public String getPoolName() { + return toString(); + } + + /** + * Returns the number of currently idling threads. + * Used from management tools only. + * + * @return number of idling threads. + */ + @Override + public int getNumberIdlingThreads() { + return 0; + } + + /** + * Returns the number of currently running threads. + * Used from management tools only. + * + * @return number of running threads. + */ + @Override + public int getNumberRunningThreads() { + return 0; + } + + /** + * Dispatch a task into the pool. + * + * @param asyncTask the task to dispatch. + */ + @Override + public void dispatchTask(AsyncTask asyncTask) { + threadpoolSwiftlet.runAsync(asyncTask, true); + } + + /** + * Freezes this pool. That is, the current running tasks are completed but + * no further tasks will be scheduled until unfreeze() is called. It is possible + * to dispatch tasks during freeze. However, these will be executed after unfreeze() + * is called. + * + * @param listener will be called when the pool is freezed. + */ + @Override + public void freeze(FreezeCompletionListener listener) { + + } + + /** + * Unfreezes this pool. + */ + @Override + public void unfreeze() { + + } + + /** + * Stops the pool. + * Internal use only. + */ + @Override + public void stop() { + + } + + @Override + public String toString() { + return IVMTaskScheduler.class.getSimpleName(); + } +} From 203a412f57a3e05c60879d6458b3f7192614a459 Mon Sep 17 00:00:00 2001 From: iitsoftware Date: Thu, 10 Apr 2025 12:33:07 +0200 Subject: [PATCH 2/8] Enhance timeout handling with nanosecond precision Refactor timeout calculation in `MessageConsumerImpl` to use `System.nanoTime()` for improved precision and reliability. Replaced the previous millisecond-based logic with a deadline-driven approach, ensuring more accurate waiting periods under various conditions. --- .../swiftmq/jms/v750/MessageConsumerImpl.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java b/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java index 3a0607e..1b321a2 100644 --- a/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java +++ b/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java @@ -33,6 +33,7 @@ import javax.jms.IllegalStateException; import javax.jms.*; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; @@ -369,14 +370,16 @@ Message receiveMessage(boolean block, long timeout) throws JMSException { if (timeout == 0) { waiter.doWait(); } else { - long to = timeout; - do { - long startWait = System.currentTimeMillis(); - waiter.doWait(to); - long delta = System.currentTimeMillis() - startWait; - to -= delta; + long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout); + long remaining; + + while ((remaining = deadline - System.nanoTime()) > 0 && + messageCache.getSize() == 0 && + fillCachePending.get() && + !cancelled.get() && + !isClosed()) { + waiter.doWait(TimeUnit.NANOSECONDS.toMillis(remaining)); } - while (to > 0 && messageCache.getSize() == 0 && fillCachePending.get() && !cancelled.get() && !isClosed()); } } else { if (fillCachePending.get() && receiveNoWaitFirstCall.get()) { From c810f9e031bdbac5ce9ea397e8e926fe1e21f976 Mon Sep 17 00:00:00 2001 From: iitsoftware Date: Thu, 10 Apr 2025 13:18:19 +0200 Subject: [PATCH 3/8] Refine message wait logic in MessageConsumerImpl Improved the wait mechanism to ensure accurate handling of timeout conditions when waiting for messages. Reworked the loop structure to simplify checks and utilize precise nanosecond-based deadlines for better reliability. --- .../com/swiftmq/jms/v750/MessageConsumerImpl.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java b/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java index 1b321a2..2edb72f 100644 --- a/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java +++ b/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java @@ -130,6 +130,7 @@ void addToCache(AsyncMessageDeliveryRequest request) { if (request.isRequiresRestart()) fillCachePending.set(false); messageCache.add(request); + waiter.signal(); } void addToCache(AsyncMessageDeliveryRequest[] requests, boolean lastRestartRequired) { @@ -372,12 +373,14 @@ Message receiveMessage(boolean block, long timeout) throws JMSException { } else { long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout); long remaining; + while (true) { + if (messageCache.getSize() > 0 || !fillCachePending.get() || cancelled.get() || isClosed()) + break; + + remaining = deadline - System.nanoTime(); + if (remaining <= 0) + break; - while ((remaining = deadline - System.nanoTime()) > 0 && - messageCache.getSize() == 0 && - fillCachePending.get() && - !cancelled.get() && - !isClosed()) { waiter.doWait(TimeUnit.NANOSECONDS.toMillis(remaining)); } } From 3806bdc2bc1c7e8b63da70cff40655f46efac064 Mon Sep 17 00:00:00 2001 From: iitsoftware Date: Thu, 10 Apr 2025 13:40:27 +0200 Subject: [PATCH 4/8] Revert "Refine message wait logic in MessageConsumerImpl" This reverts commit c810f9e031bdbac5ce9ea397e8e926fe1e21f976. --- .../com/swiftmq/jms/v750/MessageConsumerImpl.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java b/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java index 2edb72f..1b321a2 100644 --- a/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java +++ b/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java @@ -130,7 +130,6 @@ void addToCache(AsyncMessageDeliveryRequest request) { if (request.isRequiresRestart()) fillCachePending.set(false); messageCache.add(request); - waiter.signal(); } void addToCache(AsyncMessageDeliveryRequest[] requests, boolean lastRestartRequired) { @@ -373,14 +372,12 @@ Message receiveMessage(boolean block, long timeout) throws JMSException { } else { long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout); long remaining; - while (true) { - if (messageCache.getSize() > 0 || !fillCachePending.get() || cancelled.get() || isClosed()) - break; - - remaining = deadline - System.nanoTime(); - if (remaining <= 0) - break; + while ((remaining = deadline - System.nanoTime()) > 0 && + messageCache.getSize() == 0 && + fillCachePending.get() && + !cancelled.get() && + !isClosed()) { waiter.doWait(TimeUnit.NANOSECONDS.toMillis(remaining)); } } From 137a08d0292d586a4f945612c0bd27ad7902df0b Mon Sep 17 00:00:00 2001 From: iitsoftware Date: Thu, 10 Apr 2025 13:40:32 +0200 Subject: [PATCH 5/8] Revert "Enhance timeout handling with nanosecond precision" This reverts commit 203a412f57a3e05c60879d6458b3f7192614a459. --- .../swiftmq/jms/v750/MessageConsumerImpl.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java b/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java index 1b321a2..3a0607e 100644 --- a/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java +++ b/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java @@ -33,7 +33,6 @@ import javax.jms.IllegalStateException; import javax.jms.*; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; @@ -370,16 +369,14 @@ Message receiveMessage(boolean block, long timeout) throws JMSException { if (timeout == 0) { waiter.doWait(); } else { - long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout); - long remaining; - - while ((remaining = deadline - System.nanoTime()) > 0 && - messageCache.getSize() == 0 && - fillCachePending.get() && - !cancelled.get() && - !isClosed()) { - waiter.doWait(TimeUnit.NANOSECONDS.toMillis(remaining)); + long to = timeout; + do { + long startWait = System.currentTimeMillis(); + waiter.doWait(to); + long delta = System.currentTimeMillis() - startWait; + to -= delta; } + while (to > 0 && messageCache.getSize() == 0 && fillCachePending.get() && !cancelled.get() && !isClosed()); } } else { if (fillCachePending.get() && receiveNoWaitFirstCall.get()) { From 9ed05555873a43bbbd65dbe4bc65896464eb177f Mon Sep 17 00:00:00 2001 From: iitsoftware Date: Thu, 10 Apr 2025 13:56:05 +0200 Subject: [PATCH 6/8] Update timeout handling in MessageConsumerImpl Replaced millisecond-based timing with nanosecond precision for more accurate timeout calculations during message waiting. This change improves reliability in scenarios with tight timing constraints. --- src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java b/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java index 3a0607e..ac0e6a0 100644 --- a/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java +++ b/src/main/java/com/swiftmq/jms/v750/MessageConsumerImpl.java @@ -33,6 +33,7 @@ import javax.jms.IllegalStateException; import javax.jms.*; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; @@ -371,9 +372,9 @@ Message receiveMessage(boolean block, long timeout) throws JMSException { } else { long to = timeout; do { - long startWait = System.currentTimeMillis(); + long startWait = System.nanoTime(); waiter.doWait(to); - long delta = System.currentTimeMillis() - startWait; + long delta = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startWait); to -= delta; } while (to > 0 && messageCache.getSize() == 0 && fillCachePending.get() && !cancelled.get() && !isClosed()); From fea1fe1efe388ca93849a6697ef33b9e59b7ead3 Mon Sep 17 00:00:00 2001 From: iitsoftware Date: Mon, 14 Apr 2025 15:20:01 +0200 Subject: [PATCH 7/8] Add support for connection properties exchange. Introduced the ability to set and retrieve properties in OpenFrame exchanges between local and remote endpoints. This change ensures flexible configuration and access to remote properties post-connection establishment. --- .../swiftmq/amqp/v100/client/Connection.java | 21 +++++++++++++++++++ .../v100/client/ConnectionDispatcher.java | 16 +++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/swiftmq/amqp/v100/client/Connection.java b/src/main/java/com/swiftmq/amqp/v100/client/Connection.java index c0ff8b8..5cc6a7a 100644 --- a/src/main/java/com/swiftmq/amqp/v100/client/Connection.java +++ b/src/main/java/com/swiftmq/amqp/v100/client/Connection.java @@ -25,6 +25,7 @@ import com.swiftmq.amqp.v100.client.po.POOpen; import com.swiftmq.amqp.v100.client.po.POProtocolRequest; import com.swiftmq.amqp.v100.client.po.POSendClose; +import com.swiftmq.amqp.v100.generated.transport.definitions.Fields; import com.swiftmq.amqp.v100.transport.AMQPFrame; import com.swiftmq.amqp.v100.types.AMQPString; import com.swiftmq.amqp.v100.types.AMQPSymbol; @@ -92,6 +93,7 @@ public class Connection implements ExceptionHandler { int outputBufferExtendSize = 65536; SocketFactory socketFactory = new PlainSocketFactory(); ExceptionListener exceptionListener = null; + Fields properties = null; /** * Creates a Connection and uses SASL for authentication. @@ -223,6 +225,23 @@ public void setOpenHostname(String openHostname) { this.openHostname = openHostname; } + /** + * Sets the of the OpenFrame to be sent to the remote endpoint + * + * @param properties Fields for the OpenFrame + */ + public void setProperties(Fields properties) { + this.properties = properties; + } + + /** + * Retrieves the properties of the OpenFrame from the remote endpoint. Available after connect + * + * @return Fields object representing the remote properties, or null if not available + */ + public Fields getRemoteProperties() { + return connectionDispatcher.getRemoteProperties(); + } /** * Sets the idle timeout. Default is Long.MAX_VALUE. *

@@ -365,6 +384,8 @@ protected ProtocolInputHandler createInputHandler() { return connectionDispatcher.getProtocolHandler(); } }; + if (properties != null) + connectionDispatcher.setProperties(properties); connectionDispatcher.setMyConnection(this); networkConnection.start(); dos = new DataStreamOutputStream(networkConnection.getOutputStream()); diff --git a/src/main/java/com/swiftmq/amqp/v100/client/ConnectionDispatcher.java b/src/main/java/com/swiftmq/amqp/v100/client/ConnectionDispatcher.java index ec3b57f..c75db72 100644 --- a/src/main/java/com/swiftmq/amqp/v100/client/ConnectionDispatcher.java +++ b/src/main/java/com/swiftmq/amqp/v100/client/ConnectionDispatcher.java @@ -26,6 +26,7 @@ import com.swiftmq.amqp.v100.generated.security.sasl.*; import com.swiftmq.amqp.v100.generated.transport.definitions.ConnectionError; import com.swiftmq.amqp.v100.generated.transport.definitions.ErrorConditionFactory; +import com.swiftmq.amqp.v100.generated.transport.definitions.Fields; import com.swiftmq.amqp.v100.generated.transport.definitions.Milliseconds; import com.swiftmq.amqp.v100.generated.transport.performatives.*; import com.swiftmq.amqp.v100.transport.AMQPFrame; @@ -76,7 +77,7 @@ public class ConnectionDispatcher POProtocolRequest protPO = null; POAuthenticate authPO = null; POOpen openPO = null; - OpenFrame remoteOpen = null; + volatile OpenFrame remoteOpen = null; POSendClose closePO = null; CloseFrame remoteClose = null; SaslMechanismsFrame saslMechanisms = null; @@ -89,6 +90,7 @@ public class ConnectionDispatcher long idleTimeoutDelay = 0; SaslClient saslClient = null; volatile boolean connectionDisabled = false; + Fields properties = null; int maxLocalFrameSize = Integer.MAX_VALUE; int maxRemoteFrameSize = Integer.MAX_VALUE; @@ -133,6 +135,16 @@ public int getMaxFrameSize() { return Math.max(512, Math.min(maxLocalFrameSize, maxRemoteFrameSize)); } + public void setProperties(Fields properties) { + this.properties = properties; + } + + public Fields getRemoteProperties() { + if (remoteOpen != null) + return remoteOpen.getProperties(); + return null; + } + public void setSaslActive(boolean saslActive) { this.saslActive = saslActive; } @@ -317,6 +329,8 @@ public void visit(POOpen po) { OpenFrame openFrame = new OpenFrame(0); openFrame.setContainerId(new AMQPString(po.getContainerId())); openFrame.setChannelMax(new AMQPUnsignedShort(po.getMaxChannel())); + if (properties != null) + openFrame.setProperties(properties); if (myConnection.getOpenHostname() == null) openFrame.setHostname(new AMQPString(remoteHostname)); else From 2a19bf67391268191f0c778f16ec113a0774d211 Mon Sep 17 00:00:00 2001 From: iitsoftware Date: Tue, 15 Apr 2025 10:13:38 +0200 Subject: [PATCH 8/8] Update SwiftMQ client version to 13.1.2 Bumped the SwiftMQ client dependency from version 13.1.1 to 13.1.2 in the `pom.xml`. This update ensures the project uses the latest features and fixes provided in the new version. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1439b12..c00c91a 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.swiftmq swiftmq-client - 13.1.1 + 13.1.2 SwiftMQ Client Client for SwiftMQ Messaging System with JMS, AMQP 1.0 and file transfer over JMS.