diff --git a/spock-core/src/main/java/org/spockframework/mock/runtime/ByteBuddyMockFactory.java b/spock-core/src/main/java/org/spockframework/mock/runtime/ByteBuddyMockFactory.java
index 030b835b08..f4ed2ae0b6 100644
--- a/spock-core/src/main/java/org/spockframework/mock/runtime/ByteBuddyMockFactory.java
+++ b/spock-core/src/main/java/org/spockframework/mock/runtime/ByteBuddyMockFactory.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package org.spockframework.mock.runtime;
import net.bytebuddy.ByteBuddy;
@@ -13,24 +29,56 @@
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.Morph;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.VisibleForTesting;
import org.spockframework.mock.ISpockMockObject;
import org.spockframework.mock.codegen.Target;
import org.spockframework.util.Nullable;
import java.lang.reflect.Method;
import java.util.List;
+import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.none;
class ByteBuddyMockFactory {
+ /**
+ * The size of the {@link #cacheLocks}.
+ *
Caution: This must match the {@link #CACHE_LOCK_MASK}.
+ */
+ private static final int CACHE_LOCK_SIZE = 16;
+
+ /**
+ * The mask to use to mask out the {@link TypeCache.SimpleKey#hashCode()} to find the {@link #cacheLocks}.
+ *
Caution: this must match the bits of the {@link #CACHE_LOCK_SIZE}.
+ */
+ private static final int CACHE_LOCK_MASK = 0x0F;
+
+ /**
+ * This array contains {@link TypeCachingLock} instances, which are used as java monitor locks for
+ * {@link TypeCache#findOrInsert(ClassLoader, Object, Callable, Object)}.
+ * The {@code cacheLocks} spreads the lock to acquire over multiple locks instead of using a single lock
+ * {@code CACHE} for all {@code TypeCache.SimpleKeys}.
+ *
+ *
Note: We can't simply use the mockedType class lock as a lock,
+ * because the {@link TypeCache.SimpleKey}, will be the same for different {@code mockTypes + additionalInterfaces}.
+ * See the hashcode implementation of the {@code TypeCache.SimpleKey}, which has {@code Set} semantics.
+ */
+ private static final TypeCachingLock[] cacheLocks;
private static final TypeCache CACHE =
new TypeCache.WithInlineExpunction<>(TypeCache.Sort.SOFT);
private static final Class> CODEGEN_TARGET_CLASS = Target.class;
private static final String CODEGEN_PACKAGE = CODEGEN_TARGET_CLASS.getPackage().getName();
+ static {
+ cacheLocks = new TypeCachingLock[CACHE_LOCK_SIZE];
+ for (int i = 0; i < CACHE_LOCK_SIZE; i++) {
+ cacheLocks[i] = new TypeCachingLock();
+ }
+ }
+
static Object createMock(final Class> type,
final List> additionalInterfaces,
@Nullable List