|
32 | 32 | import org.apache.jmeter.assertions.AssertionResult; |
33 | 33 | import org.apache.jmeter.gui.Searchable; |
34 | 34 | import org.apache.jmeter.testelement.TestPlan; |
| 35 | +import org.apache.jmeter.threads.JMeterContext; |
| 36 | +import org.apache.jmeter.threads.JMeterContextService; |
35 | 37 | import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; |
| 38 | +import org.apache.jmeter.control.TransactionSampler; |
36 | 39 | import org.apache.jmeter.util.JMeterUtils; |
37 | 40 | import org.apache.jorphan.util.JOrphanUtils; |
38 | 41 | import org.apache.jorphan.util.StringUtilities; |
@@ -1595,10 +1598,62 @@ public void setStartNextThreadLoop(boolean startNextThreadLoop) { |
1595 | 1598 | } |
1596 | 1599 |
|
1597 | 1600 | /** |
1598 | | - * Clean up cached data |
| 1601 | + * Clean up cached data, but only if this is a root result (no parent) |
| 1602 | + * and not part of a parent transaction. |
1599 | 1603 | */ |
1600 | 1604 | public void cleanAfterSample() { |
| 1605 | + // Only clean if this is a root result (no parent) |
| 1606 | + if (parent != null) { |
| 1607 | + return; |
| 1608 | + } |
| 1609 | + |
| 1610 | + // Check if we're part of a parent transaction by walking up the sampler hierarchy |
| 1611 | + JMeterContext context = JMeterContextService.getContext(); |
| 1612 | + if (context != null) { |
| 1613 | + Sampler currentSampler = context.getCurrentSampler(); |
| 1614 | + if (currentSampler instanceof TransactionSampler) { |
| 1615 | + TransactionSampler transSampler = (TransactionSampler) currentSampler; |
| 1616 | + // Get the parent sampler from the transaction |
| 1617 | + Sampler parentSampler = transSampler.getSubSampler(); |
| 1618 | + // If there's a parent sampler and it's a transaction, we're nested |
| 1619 | + if (parentSampler instanceof TransactionSampler) { |
| 1620 | + return; |
| 1621 | + } |
| 1622 | + } |
| 1623 | + } |
| 1624 | + |
| 1625 | + cleanRecursively(); |
| 1626 | + } |
| 1627 | + |
| 1628 | + /** |
| 1629 | + * Internal method to clean this result and all its sub-results |
| 1630 | + */ |
| 1631 | + private void cleanRecursively() { |
| 1632 | + // Clean sub-results first |
| 1633 | + if (subResults != null) { |
| 1634 | + for (SampleResult subResult : subResults) { |
| 1635 | + if (subResult != null) { |
| 1636 | + subResult.cleanRecursively(); |
| 1637 | + } |
| 1638 | + } |
| 1639 | + subResults.clear(); |
| 1640 | + subResults = null; |
| 1641 | + } |
| 1642 | + |
| 1643 | + // Clean assertion results |
| 1644 | + if (assertionResults != null) { |
| 1645 | + assertionResults.clear(); |
| 1646 | + assertionResults = null; |
| 1647 | + } |
| 1648 | + |
| 1649 | + // Clear only memory-heavy data and caches, preserve samplerData |
| 1650 | + this.parent = null; |
1601 | 1651 | this.responseDataAsString = null; |
| 1652 | + this.responseData = EMPTY_BA; |
| 1653 | + this.responseHeaders = ""; |
| 1654 | + this.requestHeaders = ""; |
| 1655 | + this.samplerData = null; |
| 1656 | + |
1602 | 1657 | } |
1603 | 1658 |
|
1604 | 1659 | @Override |
@@ -1666,4 +1721,50 @@ public TestLogicalAction getTestLogicalAction() { |
1666 | 1721 | public void setTestLogicalAction(TestLogicalAction testLogicalAction) { |
1667 | 1722 | this.testLogicalAction = testLogicalAction; |
1668 | 1723 | } |
| 1724 | + |
| 1725 | + /** |
| 1726 | + * Create a deep copy for async listeners |
| 1727 | + * @return A deep copy of this result |
| 1728 | + */ |
| 1729 | + public SampleResult cloneForListeners() { |
| 1730 | + // Create clone with the correct type |
| 1731 | + SampleResult clone; |
| 1732 | + try { |
| 1733 | + // Use the same constructor that was used to create this instance |
| 1734 | + clone = this.getClass().getConstructor(this.getClass()).newInstance(this); |
| 1735 | + } catch (Exception e) { |
| 1736 | + // Fallback to base class if constructor is not available |
| 1737 | + log.debug("Could not create clone with type: " + this.getClass().getName() + ", using base class", e); |
| 1738 | + clone = new SampleResult(this); |
| 1739 | + } |
| 1740 | + |
| 1741 | + // Deep copy mutable fields that the copy constructor doesn't handle deeply |
| 1742 | + if (responseData != EMPTY_BA) { |
| 1743 | + clone.responseData = responseData.clone(); |
| 1744 | + } |
| 1745 | + |
| 1746 | + // Deep copy subResults |
| 1747 | + if (subResults != null) { |
| 1748 | + clone.subResults = new ArrayList<>(subResults.size()); |
| 1749 | + for (SampleResult sub : subResults) { |
| 1750 | + SampleResult subClone = sub.cloneForListeners(); |
| 1751 | + subClone.setParent(clone); |
| 1752 | + clone.subResults.add(subClone); |
| 1753 | + } |
| 1754 | + } |
| 1755 | + |
| 1756 | + // Deep copy assertion results |
| 1757 | + if (assertionResults != null) { |
| 1758 | + clone.assertionResults = new ArrayList<>(assertionResults.size()); |
| 1759 | + for (AssertionResult assertionResult : assertionResults) { |
| 1760 | + clone.assertionResults.add(assertionResult); // AssertionResult is immutable |
| 1761 | + } |
| 1762 | + } |
| 1763 | + |
| 1764 | + // Clear only the caches and unnecessary references in the clone |
| 1765 | + clone.responseDataAsString = null; |
| 1766 | + clone.parent = null; // Parent reference not needed in the clone |
| 1767 | + |
| 1768 | + return clone; |
| 1769 | + } |
1669 | 1770 | } |
0 commit comments