Skip to content

Commit

Permalink
fix: Use WeakHashMap for caching proxy classes (#2260)
Browse files Browse the repository at this point in the history
  • Loading branch information
Auto81 authored Jan 20, 2025
1 parent f99533c commit eac05b1
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 3 deletions.
7 changes: 4 additions & 3 deletions src/main/java/io/appium/java_client/proxy/Helpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -50,7 +50,8 @@ public class Helpers {
// the performance and to avoid extensive memory usage for our case, where
// the amount of instrumented proxy classes we create is low in comparison to the amount
// of proxy instances.
private static final ConcurrentMap<ProxyClassSignature, Class<?>> CACHED_PROXY_CLASSES = new ConcurrentHashMap<>();
private static final Map<ProxyClassSignature, Class<?>> CACHED_PROXY_CLASSES =
Collections.synchronizedMap(new WeakHashMap<>());

private Helpers() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,29 @@
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.lessThan;
import static org.openqa.selenium.support.PageFactory.initElements;


@SuppressWarnings({"unchecked", "unused"})
public class CombinedWidgetTest {

/**
* Based on how many Proxy Classes are created during this test class,
* this number is used to determine if the cache is being purged correctly between tests.
*/
private static final int THRESHOLD_SIZE = 50;

/**
* Test data generation.
*
Expand Down Expand Up @@ -57,6 +66,7 @@ public static Stream<Arguments> data() {
@ParameterizedTest
@MethodSource("data")
void checkThatWidgetsAreCreatedCorrectly(AbstractApp app, WebDriver driver, Class<?> widgetClass) {
assertProxyClassCacheGrowth();
initElements(new AppiumFieldDecorator(driver), app);
assertThat("Expected widget class was " + widgetClass.getName(),
app.getWidget().getSubWidget().getSelfReference().getClass(),
Expand Down Expand Up @@ -161,4 +171,32 @@ public List<PartiallyCombinedWidget> getWidgets() {
return multipleWidgets;
}
}


/**
* Assert proxy class cache growth for this test class.
* The (@link io.appium.java_client.proxy.Helpers#CACHED_PROXY_CLASSES) should be populated during these tests.
* Prior to the Caching issue being resolved
* - the CACHED_PROXY_CLASSES would grow indefinitely, resulting in an Out Of Memory exception.
* - this ParameterizedTest would have the CACHED_PROXY_CLASSES grow to 266 entries.
*/
private void assertProxyClassCacheGrowth() {
System.gc(); //Trying to force a collection for more accurate check numbers
assertThat(
"Proxy Class Cache threshold is " + THRESHOLD_SIZE,
getCachedProxyClassesSize(),
lessThan(THRESHOLD_SIZE)
);
}

private int getCachedProxyClassesSize() {
try {
Field cpc = Class.forName("io.appium.java_client.proxy.Helpers").getDeclaredField("CACHED_PROXY_CLASSES");
cpc.setAccessible(true);
Map<?, ?> cachedProxyClasses = (Map<?, ?>) cpc.get(null);
return cachedProxyClasses.size();
} catch (NoSuchFieldException | ClassNotFoundException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

0 comments on commit eac05b1

Please sign in to comment.