Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug 66269: Add NoThreadClone to HeaderManager to reduce heap utilization #727

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

vlsi
Copy link
Collaborator

@vlsi vlsi commented Sep 19, 2022

Motivation and Context

HeaderManager consumes heap, and it looks it can be reused between threads.

Fixes #5708

@vlsi vlsi changed the title Add NoThreadClone to HeaderManager to reduce heap utilization Bug 66269: Add NoThreadClone to HeaderManager to reduce heap utilization Sep 20, 2022
@vlsi
Copy link
Collaborator Author

vlsi commented Oct 29, 2022

The change would impact the ones who attempt to add/remove headers dynamically via JSR223 or something similar.

For instance, https://sqa.stackexchange.com/questions/41708/jmeter-header-manager-alters-the-headers-during-test attempts

sampler.getHeaderManager().add(new Header("Authorization","Bearer " + vars.get("BEARER"))); 

@vlsi vlsi force-pushed the httpmanager_noclone branch 2 times, most recently from 8b78e35 to cb51d19 Compare October 29, 2022 09:37
@vlsi
Copy link
Collaborator Author

vlsi commented Oct 29, 2022

I've added if (running version) throw IllegalStateException so the ones who accidentally run into the updating of the shared HTTP header manager would know the limitation.

I'm inclined to merge this as I do not expect it to cause major disruption. At the same time, JMeter's recorder produces header manager for every HTTP sampler, so RAM savings are significant.

@vlsi
Copy link
Collaborator Author

vlsi commented Oct 29, 2022

Well, it triggers test failures:

java.lang.IllegalStateException: Cannot modify HeaderManager while test is running. If you need dynamic headers, prefer using ${...} expressions in your headers.
	at org.apache.jmeter.protocol.http.control.HeaderManager.assertMutable(HeaderManager.java:63) ~[ApacheJMeter_http.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.protocol.http.control.HeaderManager.add(HeaderManager.java:171) ~[ApacheJMeter_http.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.protocol.http.control.HeaderManager.merge(HeaderManager.java:307) ~[ApacheJMeter_http.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.setHeaderManager(HTTPSamplerBase.java:935) ~[ApacheJMeter_http.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.addTestElement(HTTPSamplerBase.java:728) ~[ApacheJMeter_http.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.threads.TestCompiler.configureWithConfigElements(TestCompiler.java:314) ~[ApacheJMeter_core.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.threads.TestCompiler.configureSampler(TestCompiler.java:102) ~[ApacheJMeter_core.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.threads.JMeterThread.executeSamplePackage(JMeterThread.java:560) ~[ApacheJMeter_core.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.threads.JMeterThread.processSampler(JMeterThread.java:501) ~[ApacheJMeter_core.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.threads.JMeterThread.run(JMeterThread.java:268) ~[ApacheJMeter_core.jar:5.5.1-SNAPSHOT]
	at java.lang.Thread.run(Thread.java:833) ~[?:?]
2022-10-29 09:48:54,465 ERROR o.a.j.t.JMeterThread: Error while processing sampler: 'HR-BROTLI'.
java.lang.IllegalStateException: Cannot modify HeaderManager while test is running. If you need dynamic headers, prefer using ${...} expressions in your headers.
	at org.apache.jmeter.protocol.http.control.HeaderManager.assertMutable(HeaderManager.java:63) ~[ApacheJMeter_http.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.protocol.http.control.HeaderManager.add(HeaderManager.java:171) ~[ApacheJMeter_http.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.protocol.http.control.HeaderManager.merge(HeaderManager.java:307) ~[ApacheJMeter_http.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.setHeaderManager(HTTPSamplerBase.java:935) ~[ApacheJMeter_http.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.addTestElement(HTTPSamplerBase.java:728) ~[ApacheJMeter_http.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.threads.TestCompiler.configureWithConfigElements(TestCompiler.java:314) ~[ApacheJMeter_core.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.threads.TestCompiler.configureSampler(TestCompiler.java:102) ~[ApacheJMeter_core.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.threads.JMeterThread.executeSamplePackage(JMeterThread.java:560) ~[ApacheJMeter_core.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.threads.JMeterThread.processSampler(JMeterThread.java:501) ~[ApacheJMeter_core.jar:5.5.1-SNAPSHOT]
	at org.apache.jmeter.threads.JMeterThread.run(JMeterThread.java:268) ~[ApacheJMeter_core.jar:5.5.1-SNAPSHOT]
	at java.lang.Thread.run(Thread.java:833) ~[?:?]
:src:dist-check:batchResponseDecompression

@FSchumacher
Copy link
Contributor

The change would impact the ones who attempt to add/remove headers dynamically via JSR223 or something similar.

For instance, https://sqa.stackexchange.com/questions/41708/jmeter-header-manager-alters-the-headers-during-test attempts

sampler.getHeaderManager().add(new Header("Authorization","Bearer " + vars.get("BEARER"))); 

Don't you think, this is done more often, than we think about it? I think we have recommended such a workaround for other problems with the samplers setting/not setting headers in the past.

Can we implement a switch to enable sharing of the header manager and expose it with a property (set default to share instances)?

@vlsi vlsi added this to the 5.6 milestone May 10, 2023
if (isRunningVersion()) {
throw new IllegalStateException(
"Cannot modify HeaderManager " + getName() + " while test is running. " +
"If you need dynamic headers, prefer using ${...} expressions in your headers."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vlsi probably better to use JMeter functions than "${...} expressions", because in JMeter's manuel the terms is JMeter functions and variables https://jmeter.apache.org/usermanual/functions.html

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the manual says "Functions and Variables". Do you suggest If you need dynamic headers, prefer using ${...} functions or variables in your headers?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, I think it makes sense to add a common name for "JMeter expression language"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure that the functions in JMeter can be call "expression language", but why not. if you would change to "JMeter expression language", then the manual need to be update (remplace the terms "functions" to "JMeter expression language"), and in JMeter self (menu Tools > Function Helper Dialog)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function Helper Dialog generates functions. I'm not sure it can generate variables.


expression language

Well, the language is something that declares parsing and semantic rules.
For instance, neither function nor variable can specify if nested ${...} are allowed. Neither function nor variable can specify the way to escape $ if the user needs literal $ value without replacements.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, in the context of this failure message "function or variable" sounds good enough

@pmouawad
Copy link
Contributor

The change would impact the ones who attempt to add/remove headers dynamically via JSR223 or something similar.

For instance, https://sqa.stackexchange.com/questions/41708/jmeter-header-manager-alters-the-headers-during-test attempts

sampler.getHeaderManager().add(new Header("Authorization","Bearer " + vars.get("BEARER"))); 

Hello @vlsi , how would people requiring this feature do then ?
In my performance testing experience I know that for example to be able to test JDEdwards (and another custom product), you need similar code. So if it's not more possible, it's an important problem.

Thanks

@pmouawad
Copy link
Contributor

getHeaderManager().add(

I think it's fine through a PreProcessor if you confirm dynamic Headers would still work

vlsi added 2 commits May 12, 2023 15:36
…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.
@@ -602,6 +602,23 @@ JMETER-SERVER</source>
</property>
</properties>
</section>

<section name="&sect-num;.14 HTTP Header Manager configuration" anchor="http_header_manager">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that &sect-num;.14 duplicates the number of the previous section, however, I believe we'll remove section numbers in #5915

@vlsi
Copy link
Collaborator Author

vlsi commented May 12, 2023

It turns out the case is harder than I thought.
When the script has several HTTP Header Manager components at different levels, then the current JMeter approach is to merge header managers with org.apache.jmeter.protocol.http.control.HeaderManager#merge(org.apache.jmeter.testelement.TestElement)

Apparently, that fails in case HTTP Header Managers are shared across threads.

The next issue is that the "temporary" header managers are not reset after HTTP sampler execution, and they are re-created only before the next execution. In other words, even if we eliminate HTTP Header Manager cloning "before thread starts", then it would generate copies during test execution anyway.

The worst thing is that org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#getHeaderManager() returns HeaderManager is a public method, so changing it would likely affect backward compatibility.

Frankly speaking, I'm quite disappointed by all that.

@vlsi vlsi modified the milestones: 5.6, 5.7 May 13, 2023
@vlsi
Copy link
Collaborator Author

vlsi commented May 13, 2023

Based on the analysis above, I'm moving this PR to the next milestone, so it no longer holds the release.

I wish we could deprecate and drop org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#getHeaderManager, and org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#setHeaderManager.

@vlsi
Copy link
Collaborator Author

vlsi commented May 13, 2023

I would push a PR that deprecates the problematic methods. There's a chance the users would notice it and stop using it or ask for a raplacement

@vlsi vlsi marked this pull request as draft May 13, 2023 12:40
vlsi added a commit to vlsi/jmeter that referenced this pull request May 13, 2023
vlsi added a commit to vlsi/jmeter that referenced this pull request May 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

HeaderManager instances could be reused across threads
4 participants