From 2176b339a41e5b8f36b578d761c24eedc8051b58 Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Fri, 12 May 2023 15:35:50 +0300 Subject: [PATCH 1/2] Add NoThreadClone#isShareable method so the elements can dynamically decide if they need cloning or not Previously, components could implements NoThreadClone to prevent cloning in each thread, however, there was no way to "unimplement" the interface. By default, isShareable returns true, so existing components work as before. --- .../main/java/org/apache/jmeter/engine/TreeCloner.java | 2 +- .../org/apache/jmeter/engine/util/NoThreadClone.java | 9 +++++++++ .../apache/jmeter/testelement/AbstractTestElement.java | 2 +- xdocs/changes.xml | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/engine/TreeCloner.java b/src/core/src/main/java/org/apache/jmeter/engine/TreeCloner.java index fddc404ab1c..961a2d24e4f 100644 --- a/src/core/src/main/java/org/apache/jmeter/engine/TreeCloner.java +++ b/src/core/src/main/java/org/apache/jmeter/engine/TreeCloner.java @@ -71,7 +71,7 @@ public final void addNode(Object node, HashTree subTree) { protected Object addNodeToTree(Object node) { if ( (node instanceof TestElement) // Check can cast for clone // Don't clone NoThreadClone unless honourNoThreadClone == false - && !(honourNoThreadClone && node instanceof NoThreadClone) + && !(honourNoThreadClone && node instanceof NoThreadClone && ((NoThreadClone) node).isShareable()) ) { Object newNode = ((TestElement) node).clone(); newTree.add(objects, newNode); diff --git a/src/core/src/main/java/org/apache/jmeter/engine/util/NoThreadClone.java b/src/core/src/main/java/org/apache/jmeter/engine/util/NoThreadClone.java index e2d3a9a1c3d..ccbc951cf8c 100644 --- a/src/core/src/main/java/org/apache/jmeter/engine/util/NoThreadClone.java +++ b/src/core/src/main/java/org/apache/jmeter/engine/util/NoThreadClone.java @@ -24,4 +24,13 @@ * */ public interface NoThreadClone { + /** + * Allows element to indicate whether it can be shared between threads. + * By default, elements that implement {@code NoThreadClone} are shareable. + * + * @return true if the element is shareable between threads, so it won't be cloned + */ + default boolean isShareable() { + return true; + } } diff --git a/src/core/src/main/java/org/apache/jmeter/testelement/AbstractTestElement.java b/src/core/src/main/java/org/apache/jmeter/testelement/AbstractTestElement.java index 697d5e86701..9f8511d1dd4 100644 --- a/src/core/src/main/java/org/apache/jmeter/testelement/AbstractTestElement.java +++ b/src/core/src/main/java/org/apache/jmeter/testelement/AbstractTestElement.java @@ -503,7 +503,7 @@ public void setRunningVersion(boolean runningVersion) { */ @Override public void recoverRunningVersion() { - if (this instanceof NoThreadClone) { + if (this instanceof NoThreadClone && ((NoThreadClone) this).isShareable()) { // The element is shared between threads, so there's nothing to recover // See https://github.com/apache/jmeter/issues/5875 return; diff --git a/xdocs/changes.xml b/xdocs/changes.xml index 449fa8f4818..d351caeec73 100644 --- a/xdocs/changes.xml +++ b/xdocs/changes.xml @@ -108,6 +108,7 @@ Summary Previously it cached the values based on iteration number only which triggered wrong results on concurrent executions. The previous behavior can be temporary restored with function.cache.per.iteration property. +
  • Add NoThreadClone#isShareable method so the elements can dynamically decide if they need cloning or not
  • Non-functional changes From ad0e8e9de2fb473dcad3405863aec571b64ca820 Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Mon, 19 Sep 2022 20:22:22 +0300 Subject: [PATCH 2/2] Save memory when test plan contains lots of HTTP Header Manager elements by sharing the managers across threads --- bin/jmeter.properties | 9 +++++++ .../protocol/http/control/HeaderManager.java | 24 ++++++++++++++++++- xdocs/changes.xml | 1 + xdocs/usermanual/properties_reference.xml | 17 +++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/bin/jmeter.properties b/bin/jmeter.properties index 410ae168199..6d4ed9e1f17 100644 --- a/bin/jmeter.properties +++ b/bin/jmeter.properties @@ -488,6 +488,15 @@ remote_hosts=127.0.0.1 # RETURN_CUSTOM_STATUS.code= # RETURN_CUSTOM_STATUS.message= +#--------------------------------------------------------------------------- +# HTTP Header Manager configuration +#--------------------------------------------------------------------------- +# Save memory by sharing HTTP Header Manager across threads. +# The drawback is that HTTP Header Manager's can't be modified in the runtime +# as all the threads would notice the change. +# If you need dynamic headers, consider using ${..} functions or variables +#http.header_manager.is_shareable=true + #--------------------------------------------------------------------------- # Results file configuration #--------------------------------------------------------------------------- diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/HeaderManager.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/HeaderManager.java index 5b63c98b491..84c19bb69e4 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/HeaderManager.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/HeaderManager.java @@ -29,10 +29,12 @@ import java.util.List; import org.apache.jmeter.config.ConfigTestElement; +import org.apache.jmeter.engine.util.NoThreadClone; import org.apache.jmeter.gui.Replaceable; import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.testelement.property.CollectionProperty; import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.util.JOrphanUtils; /** @@ -40,7 +42,7 @@ * with a request. * */ -public class HeaderManager extends ConfigTestElement implements Serializable, Replaceable { +public class HeaderManager extends ConfigTestElement implements Serializable, Replaceable, NoThreadClone { private static final long serialVersionUID = 240L; @@ -57,9 +59,24 @@ public HeaderManager() { setProperty(new CollectionProperty(HEADERS, new ArrayList<>())); } + @Override + public boolean isShareable() { + return JMeterUtils.getPropDefault("http.header_manager.is_shareable", true); // $NON-NLS-1$ + } + + private void assertMutable() { + if (isRunningVersion()) { + throw new IllegalStateException( + "Cannot modify HeaderManager " + getName() + " while test is running. " + + "If you need dynamic headers, prefer using ${...} functions in variables if you need dynamic headers." + ); + } + } + /** {@inheritDoc} */ @Override public void clear() { + assertMutable(); super.clear(); setProperty(new CollectionProperty(HEADERS, new ArrayList<>())); } @@ -160,6 +177,7 @@ public void addFile(String headerFile) throws IOException { * @param h {@link Header} to add */ public void add(Header h) { + assertMutable(); getHeaders().addItem(h); } @@ -167,6 +185,7 @@ public void add(Header h) { * Add an empty header. */ public void add() { + assertMutable(); getHeaders().addItem(new Header()); } @@ -176,6 +195,7 @@ public void add() { * @param index index from the header to remove */ public void remove(int index) { + assertMutable(); getHeaders().remove(index); } @@ -224,6 +244,7 @@ public Header getFirstHeaderNamed(final String name) { * @param name header name */ public void removeHeaderNamed(String name) { + assertMutable(); List removeIndices = new ArrayList<>(); for (int i = getHeaders().size() - 1; i >= 0; i--) { Header header = (Header) getHeaders().get(i).getObjectValue(); @@ -304,6 +325,7 @@ public HeaderManager merge(TestElement element) { @Override public int replace(String regex, String replaceBy, boolean caseSensitive) throws Exception { + assertMutable(); final CollectionProperty hdrs = getHeaders(); int totalReplaced = 0; for (int i = 0; i < hdrs.size(); i++) { diff --git a/xdocs/changes.xml b/xdocs/changes.xml index d351caeec73..dd2b1d01976 100644 --- a/xdocs/changes.xml +++ b/xdocs/changes.xml @@ -75,6 +75,7 @@ Summary

    HTTP Samplers and Test Script Recorder

    • 5911 Use Caffeine for caching HTTP headers instead of commons-collections4 LRUMap
    • +
    • 727 Save memory when test plan contains lots of HTTP Header Manager elements by sharing the managers across threads (add NoThreadClone to HeaderManager)

    Other samplers

    diff --git a/xdocs/usermanual/properties_reference.xml b/xdocs/usermanual/properties_reference.xml index cb32afcefa9..37dfba3e453 100644 --- a/xdocs/usermanual/properties_reference.xml +++ b/xdocs/usermanual/properties_reference.xml @@ -602,6 +602,23 @@ JMETER-SERVER + +
    + + + By default, HTTP Header Manager elements are shared between threads. It enables to save memory by not + cloning the elements for each thread, however, the drawback is that HTTP Header Manager's can't be + modified in the runtime.
    + However, HTTP Header Managers still support ${...} functions and variables, so it is still + possible to have dynamic headers. +
    + Defaults to: + true +
    +
    +
    + +