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/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/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 449fa8f4818..dd2b1d01976 100644 --- a/xdocs/changes.xml +++ b/xdocs/changes.xml @@ -75,6 +75,7 @@ Summary

HTTP Samplers and Test Script Recorder

Other samplers

@@ -108,6 +109,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 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 +
    +
    +
    + +