|
| 1 | +/* |
| 2 | + * Copyright 2023 the original author or authors. |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * https://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
1 | 17 | package org.spockframework.mock.runtime; |
2 | 18 |
|
3 | 19 | import net.bytebuddy.ByteBuddy; |
|
13 | 29 | import net.bytebuddy.implementation.MethodDelegation; |
14 | 30 | import net.bytebuddy.implementation.bind.annotation.Morph; |
15 | 31 | import org.jetbrains.annotations.NotNull; |
| 32 | +import org.jetbrains.annotations.VisibleForTesting; |
16 | 33 | import org.spockframework.mock.ISpockMockObject; |
17 | 34 | import org.spockframework.mock.codegen.Target; |
18 | 35 | import org.spockframework.util.Nullable; |
19 | 36 |
|
20 | 37 | import java.lang.reflect.Method; |
21 | 38 | import java.util.List; |
| 39 | +import java.util.concurrent.Callable; |
22 | 40 | import java.util.concurrent.ThreadLocalRandom; |
23 | 41 |
|
24 | 42 | import static net.bytebuddy.matcher.ElementMatchers.any; |
25 | 43 | import static net.bytebuddy.matcher.ElementMatchers.none; |
26 | 44 |
|
27 | 45 | class ByteBuddyMockFactory { |
| 46 | + /** |
| 47 | + * The mask to use to mask out the {@link TypeCache.SimpleKey#hashCode()} to find the {@link #cacheLocks}. |
| 48 | + */ |
| 49 | + private static final int CACHE_LOCK_MASK = 0x0F; |
| 50 | + |
| 51 | + /** |
| 52 | + * The size of the {@link #cacheLocks}. |
| 53 | + */ |
| 54 | + private static final int CACHE_LOCK_SIZE = CACHE_LOCK_MASK + 1; |
28 | 55 |
|
29 | | - private static final TypeCache<TypeCache.SimpleKey> CACHE = |
30 | | - new TypeCache.WithInlineExpunction<>(TypeCache.Sort.SOFT); |
31 | 56 | private static final Class<?> CODEGEN_TARGET_CLASS = Target.class; |
32 | 57 | private static final String CODEGEN_PACKAGE = CODEGEN_TARGET_CLASS.getPackage().getName(); |
33 | 58 |
|
34 | | - static Object createMock(final Class<?> type, |
| 59 | + /** |
| 60 | + * This array contains {@link TypeCachingLock} instances, which are used as java monitor locks for |
| 61 | + * {@link TypeCache#findOrInsert(ClassLoader, Object, Callable, Object)}. |
| 62 | + * The {@code cacheLocks} spreads the lock to acquire over multiple locks instead of using a single lock |
| 63 | + * {@code CACHE} for all {@link TypeCache.SimpleKey}s. |
| 64 | + * |
| 65 | + * <p>Note: We can't simply use the mockedType class lock as a lock, |
| 66 | + * because the {@code TypeCache.SimpleKey}, will be the same for different {@code mockTypes + additionalInterfaces}. |
| 67 | + * See the {@code hashCode} implementation of the {@code TypeCache.SimpleKey}, which has {@code Set} semantics. |
| 68 | + */ |
| 69 | + private final TypeCachingLock[] cacheLocks; |
| 70 | + |
| 71 | + private final TypeCache<TypeCache.SimpleKey> CACHE = |
| 72 | + new TypeCache.WithInlineExpunction<>(TypeCache.Sort.SOFT); |
| 73 | + |
| 74 | + ByteBuddyMockFactory() { |
| 75 | + cacheLocks = new TypeCachingLock[CACHE_LOCK_SIZE]; |
| 76 | + for (int i = 0; i < CACHE_LOCK_SIZE; i++) { |
| 77 | + cacheLocks[i] = new TypeCachingLock(); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + Object createMock(final Class<?> type, |
35 | 82 | final List<Class<?>> additionalInterfaces, |
36 | 83 | @Nullable List<Object> constructorArgs, |
37 | 84 | IProxyBasedMockInterceptor interceptor, |
38 | 85 | final ClassLoader classLoader, |
39 | 86 | boolean useObjenesis) { |
40 | 87 |
|
| 88 | + TypeCache.SimpleKey key = new TypeCache.SimpleKey(type, additionalInterfaces); |
| 89 | + |
41 | 90 | Class<?> enhancedType = CACHE.findOrInsert(classLoader, |
42 | | - new TypeCache.SimpleKey(type, additionalInterfaces), |
| 91 | + key, |
43 | 92 | () -> { |
44 | 93 | String typeName = type.getName(); |
45 | 94 | Class<?> targetClass = type; |
@@ -68,14 +117,28 @@ static Object createMock(final Class<?> type, |
68 | 117 | .make() |
69 | 118 | .load(classLoader, strategy) |
70 | 119 | .getLoaded(); |
71 | | - }, CACHE); |
| 120 | + }, getCacheLockForKey(key)); |
72 | 121 |
|
73 | 122 | Object proxy = MockInstantiator.instantiate(type, enhancedType, constructorArgs, useObjenesis); |
74 | 123 | ((ByteBuddyInterceptorAdapter.InterceptorAccess) proxy).$spock_set(interceptor); |
75 | 124 | return proxy; |
76 | 125 | } |
77 | 126 |
|
78 | | - // This methods and the ones it calls are inspired by similar code in Mockito's SubclassBytecodeGenerator |
| 127 | + /** |
| 128 | + * Returns a {@link TypeCachingLock}, which locks the {@link TypeCache#findOrInsert(ClassLoader, Object, Callable, Object)}. |
| 129 | + * |
| 130 | + * @param key the key to lock |
| 131 | + * @return the {@link TypeCachingLock} to use to lock the {@link TypeCache} |
| 132 | + */ |
| 133 | + private TypeCachingLock getCacheLockForKey(TypeCache.SimpleKey key) { |
| 134 | + int hashCode = key.hashCode(); |
| 135 | + // Try to spread some higher bits with XOR to lower bits, because we only use lower bits. |
| 136 | + hashCode = hashCode ^ (hashCode >>> 16); |
| 137 | + int index = hashCode & CACHE_LOCK_MASK; |
| 138 | + return cacheLocks[index]; |
| 139 | + } |
| 140 | + |
| 141 | + // This method and the ones it calls are inspired by similar code in Mockito's SubclassBytecodeGenerator |
79 | 142 | private static boolean shouldLoadIntoCodegenPackage(Class<?> type) { |
80 | 143 | return isComingFromJDK(type) || isComingFromSignedJar(type) || isComingFromSealedPackage(type); |
81 | 144 | } |
@@ -112,4 +175,6 @@ private static ClassLoadingStrategy<ClassLoader> determineBestClassLoadingStrate |
112 | 175 | return ClassLoadingStrategy.Default.WRAPPER; |
113 | 176 | } |
114 | 177 |
|
| 178 | + private static final class TypeCachingLock { |
| 179 | + } |
115 | 180 | } |
0 commit comments