This is an automated email from the ASF dual-hosted git repository.

ifesdjeen pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/cassandra-simulator.git

commit 9b9876680c64f60cc5e0c4ed3b7fafed8ff4a0eb
Author: Alex Petrov <[email protected]>
AuthorDate: Mon Jul 13 13:43:46 2026 +0000

    Refine simulator class loading and isolation
    
    Introduce a dedicated ClassLoadingPolicy to centralize shared-vs-isolated 
class resolution, including explicit shared/isolated overrides, 
annotation-based decisions, and default shared filtering.
    
    Rename InstanceClassLoader to IsolatedClassLoader to better reflect its 
role, and update SimulationCluster and Simulator to construct and use the new 
policy consistently.
    
    Also move simulator tests into the simulator_test package, relocate 
SimulatorProperties into the simulator package, and expand coverage around 
class routing and park/unpark behavior under simulation.
---
 .../cassandra/simulator_test/StepApiTest.java      |   2 +-
 .../simulator/context/ClassLoadingPolicy.java      | 143 +++++++++++++++++++++
 .../simulator/context/IClassTransformer.java       |   4 +-
 ...ceClassLoader.java => IsolatedClassLoader.java} |  55 +++-----
 .../simulator/context/SharedTestState.java         |   2 +-
 .../simulator/context/SimulationCluster.java       |  30 ++---
 .../simulator/context/SimulationContext.java       |   8 +-
 .../SimulationContextClassLoaderOwnershipTest.java |  38 ++++--
 .../SimulationContextClassRoutingTest.java         |   6 +-
 .../SimulationContextIntegrationTest.java          |   2 +-
 .../SimulationContextReturnTransferTest.java       |   4 +-
 .../SimulationContextSharedClassTest.java          |  43 ++++++-
 .../tests => simulator_test}/TestUtils.java        |   2 +-
 .../org/apache/cassandra/simulator/Action.java     |   2 +-
 .../apache/cassandra/simulator/ActionSchedule.java |   2 +-
 .../org/apache/cassandra/simulator/Ordered.java    |   2 +-
 .../org/apache/cassandra/simulator/Simulator.java  |  42 +++---
 .../{config => simulator}/SimulatorProperties.java |   2 +-
 .../simulator/systems/InterceptingExecutor.java    |   2 +-
 .../systems/InterceptingGlobalMethods.java         |   5 +-
 .../simulator/systems/InterceptingMonitors.java    |   2 +-
 .../cassandra/simulator_test/ParkUnparkTest.java   | 112 +++++++++++++---
 22 files changed, 376 insertions(+), 134 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/cassandra/simulator_test/StepApiTest.java
 
b/integration-test/src/test/java/org/apache/cassandra/simulator_test/StepApiTest.java
index 1c7f4f1..8b09607 100644
--- 
a/integration-test/src/test/java/org/apache/cassandra/simulator_test/StepApiTest.java
+++ 
b/integration-test/src/test/java/org/apache/cassandra/simulator_test/StepApiTest.java
@@ -52,7 +52,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  * <h2>Shared state</h2>
  *
  * {@link SharedTestState} lives in {@code 
org.apache.cassandra.simulator.context}, which
- * {@link 
org.apache.cassandra.simulator.context.InstanceClassLoader#DEFAULT_SHARED_FILTER}
+ * {@link 
org.apache.cassandra.simulator.context.IsolatedClassLoader#DEFAULT_SHARED_FILTER}
  * marks as always-shared.  The <em>same</em> {@code Class} object (and 
therefore the same
  * static {@code AtomicInteger} instances) is seen from both the outer test 
thread and from
  * code running inside the instance classloader.  No serialization barrier 
applies to
diff --git 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/ClassLoadingPolicy.java
 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/ClassLoadingPolicy.java
new file mode 100644
index 0000000..1666ac3
--- /dev/null
+++ 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/ClassLoadingPolicy.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *     http://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.apache.cassandra.simulator.context;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * Shared-vs-isolated classloading policy for IsolatedClassLoader.
+ *
+ * Precedence:
+ * 1. explicit isolated override
+ * 2. explicit shared override
+ * 3. @Isolated annotation
+ * 4. @Shared annotation
+ * 5. default shared filter
+ * 6. otherwise isolated
+ */
+public final class ClassLoadingPolicy
+{
+    public enum Decision { SHARED, ISOLATED }
+
+    private final ClassLoader sharedClassLoader;
+    private final Set<String> explicitShared;
+    private final Set<String> explicitIsolated;
+    private final Predicate<String> defaultShared;
+    private ClassLoadingPolicy(ClassLoader sharedClassLoader,
+                               Set<String> explicitShared,
+                               Set<String> explicitIsolated,
+                               Predicate<String> defaultShared)
+    {
+        this.sharedClassLoader = sharedClassLoader;
+        this.explicitShared = explicitShared;
+        this.explicitIsolated = explicitIsolated;
+        this.defaultShared = defaultShared;
+    }
+
+    public Decision decide(String className)
+    {
+        return computeDecision(className);
+    }
+
+    public boolean isShared(String className)
+    {
+        return decide(className) == Decision.SHARED;
+    }
+
+    private Decision computeDecision(String className)
+    {
+        if (explicitIsolated.contains(className)) return Decision.ISOLATED;
+        if (explicitShared.contains(className)) return Decision.SHARED;
+
+        Class<?> maybeShared = tryLoadOnSharedSide(className);
+        if (maybeShared != null)
+        {
+            if (maybeShared.getAnnotation(Isolated.class) != null)
+                return Decision.ISOLATED;
+            if (maybeShared.getAnnotation(Shared.class) != null)
+                return Decision.SHARED;
+        }
+
+        return defaultShared.test(className) ? Decision.SHARED : 
Decision.ISOLATED;
+    }
+
+    private Class<?> tryLoadOnSharedSide(String className)
+    {
+        try
+        {
+            return Class.forName(className, false, sharedClassLoader);
+        }
+        catch (ClassNotFoundException | LinkageError e)
+        {
+            return null;
+        }
+    }
+
+    public static Builder builder(ClassLoader sharedClassLoader)
+    {
+        return new Builder(sharedClassLoader);
+    }
+
+    public static final class Builder
+    {
+        private final ClassLoader sharedClassLoader;
+        private final Set<String> explicitShared = new LinkedHashSet<>();
+        private final Set<String> explicitIsolated = new LinkedHashSet<>();
+        private Predicate<String> defaultShared = name -> false;
+
+        private Builder(ClassLoader sharedClassLoader)
+        {
+            this.sharedClassLoader = sharedClassLoader;
+        }
+
+        public Builder withSharedClasses(Set<String> classNames)
+        {
+            explicitShared.addAll(classNames);
+            return this;
+        }
+
+        public Builder withIsolatedClasses(Set<String> classNames)
+        {
+            explicitIsolated.addAll(classNames);
+            return this;
+        }
+
+        public Builder withDefaultSharedFilter(Predicate<String> defaultShared)
+        {
+            this.defaultShared = defaultShared;
+            return this;
+        }
+
+        public ClassLoadingPolicy build()
+        {
+            Set<String> overlap = new LinkedHashSet<>(explicitShared);
+            overlap.retainAll(explicitIsolated);
+            if (!overlap.isEmpty())
+                throw new IllegalArgumentException("classes appear in both 
shared and isolated sets: " + overlap);
+
+            return new ClassLoadingPolicy(sharedClassLoader,
+                                          Collections.unmodifiableSet(new 
LinkedHashSet<>(explicitShared)),
+                                          Collections.unmodifiableSet(new 
LinkedHashSet<>(explicitIsolated)),
+                                          defaultShared);
+        }
+    }
+}
diff --git 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/IClassTransformer.java
 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/IClassTransformer.java
index ff31c70..e42314d 100644
--- 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/IClassTransformer.java
+++ 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/IClassTransformer.java
@@ -19,8 +19,8 @@
 package org.apache.cassandra.simulator.context;
 
 /**
- * Optional bytecode transformer applied when an {@link InstanceClassLoader} 
defines a class.
- * Returning {@code null} or the original bytes leaves the class unmodified.
+ * Optional bytecode transformer applied when an IsolatedClassLoader defines a 
class.
+ * Returning null or the original bytes leaves the class unmodified.
  */
 public interface IClassTransformer
 {
diff --git 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/InstanceClassLoader.java
 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/IsolatedClassLoader.java
similarity index 64%
rename from 
simulator-context/src/main/java/org/apache/cassandra/simulator/context/InstanceClassLoader.java
rename to 
simulator-context/src/main/java/org/apache/cassandra/simulator/context/IsolatedClassLoader.java
index 417b06a..68025f0 100644
--- 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/InstanceClassLoader.java
+++ 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/IsolatedClassLoader.java
@@ -27,32 +27,17 @@ import java.util.function.Predicate;
 /**
  * Per-context classloader that isolates application classes from other 
contexts and from
  * the host JVM.
- *
- * Application classes are loaded independently from {@code urls} and passed 
through the
- * ASM transformer, giving each context its own copy with independent static 
state and
- * rewritten call sites.
- *
- * JDK classes, the simulator framework ({@code 
org.apache.cassandra.simulator.context.*}),
- * and logging classes are excluded: they are delegated to {@code 
sharedClassLoader} and
- * reach the JVM unmodified, so every context and the host thread share a 
single
- * {@code Class} object for them.
- *
- * {@link #getDefaultSharedFilter()} enumerates the packages that are always 
shared.
  */
-public class InstanceClassLoader extends URLClassLoader
+public class IsolatedClassLoader extends URLClassLoader
 {
-    /** Generation counter — incremented on context restart so restarted 
contexts get a fresh loader. */
     public final int generation;
-
-    /** 1-based context identifier. */
     public final int id;
 
     private final ClassLoader sharedClassLoader;
-    private final Predicate<String> loadShared;
+    private final ClassLoadingPolicy policy;
     private final IClassTransformer transformer;
 
-    // Packages that are always loaded from the shared/bootstrap classloader.
-    private static final Predicate<String> DEFAULT_SHARED_FILTER = name ->
+    static final Predicate<String> DEFAULT_SHARED_FILTER = name ->
         name.startsWith("java.")
         || name.startsWith("javax.")
         || name.startsWith("jdk.")
@@ -60,29 +45,28 @@ public class InstanceClassLoader extends URLClassLoader
         || name.startsWith("com.sun.")
         || name.startsWith("org.xml.")
         || name.startsWith("org.w3c.")
-        // simulator framework classes must be shared so cross-CL method calls 
work
         || name.startsWith("org.apache.cassandra.simulator.context.")
-        // logging is shared so output goes to one place
         || name.startsWith("org.slf4j.")
         || name.startsWith("ch.qos.logback.");
 
-    public InstanceClassLoader(int generation, int id, URL[] urls,
+    public IsolatedClassLoader(int generation, int id, URL[] urls,
                                ClassLoader sharedClassLoader,
-                               Predicate<String> loadShared,
+                               ClassLoadingPolicy policy,
                                IClassTransformer transformer)
     {
         super(urls, null /* no parent — we route explicitly */);
 
-        assert urls != null && urls.length > 0 : "InstanceClassLoader for 
context " + id + " has empty classpath";
+        assert urls != null && urls.length > 0 : "IsolatedClassLoader for 
context " + id + " has empty classpath";
         assert sharedClassLoader != null        : "sharedClassLoader must not 
be null";
+        assert policy != null                   : "policy must not be null";
         assert generation >= 0                  : "generation must be 
non-negative, got " + generation;
         assert id >= 1                          : "context id must be 1-based, 
got " + id;
 
-        this.generation       = generation;
-        this.id               = id;
+        this.generation = generation;
+        this.id = id;
         this.sharedClassLoader = sharedClassLoader;
-        this.loadShared       = loadShared;
-        this.transformer      = transformer;
+        this.policy = policy;
+        this.transformer = transformer;
     }
 
     @Override
@@ -94,10 +78,7 @@ public class InstanceClassLoader extends URLClassLoader
             if (cached != null)
                 return cached;
 
-            // Delegate to the shared classloader when the predicate says so.
-            // The predicate already incorporates DEFAULT_SHARED_FILTER; 
explicit
-            // isolation always takes priority over package-prefix defaults.
-            if (loadShared.test(name))
+            if (policy.isShared(name))
             {
                 Class<?> c = sharedClassLoader.loadClass(name);
                 assert isSharedOrBootstrap(c)
@@ -105,10 +86,9 @@ public class InstanceClassLoader extends URLClassLoader
                 return c;
             }
 
-            // Load from our own URLs, applying optional bytecode 
transformation.
             Class<?> c = findAndDefine(name);
             assert c.getClassLoader() == this
-                   : "class '" + name + "' routed to instance CL but loaded by 
" + c.getClassLoader();
+                   : "class '" + name + "' routed to isolated CL but loaded by 
" + c.getClassLoader();
 
             if (resolve)
                 resolveClass(c);
@@ -140,20 +120,15 @@ public class InstanceClassLoader extends URLClassLoader
         }
     }
 
-    /** Returns true if {@code c} was loaded by the shared or a 
bootstrap/platform classloader. */
     private boolean isSharedOrBootstrap(Class<?> c)
     {
         ClassLoader cl = c.getClassLoader();
-        return cl == null                                    // bootstrap
+        return cl == null
                || cl == ClassLoader.getPlatformClassLoader()
                || cl == ClassLoader.getSystemClassLoader()
                || cl == sharedClassLoader;
     }
 
-    /**
-     * Returns the predicate that is always applied before the caller-supplied 
one.
-     * Covers JVM internals and the simulator framework itself.
-     */
     public static Predicate<String> getDefaultSharedFilter()
     {
         return DEFAULT_SHARED_FILTER;
@@ -162,6 +137,6 @@ public class InstanceClassLoader extends URLClassLoader
     @Override
     public String toString()
     {
-        return "InstanceClassLoader{id=" + id + ", generation=" + generation + 
"}";
+        return "IsolatedClassLoader{id=" + id + ", generation=" + generation + 
'}';
     }
 }
diff --git 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SharedTestState.java
 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SharedTestState.java
index 568ae9e..861771c 100644
--- 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SharedTestState.java
+++ 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SharedTestState.java
@@ -24,7 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger;
  * Shared mutable state for step-by-step simulator tests.
  *
  * This class MUST live in the {@code org.apache.cassandra.simulator.context} 
package
- * because {@link InstanceClassLoader#DEFAULT_SHARED_FILTER} hard-codes that 
package
+ * because IsolatedClassLoader.DEFAULT_SHARED_FILTER hard-codes that package
  * as always-shared.  When instrumented code running inside the instance 
classloader
  * touches {@code SharedTestState.threadRan}, the JVM delegates the class 
lookup to
  * the shared (system) classloader, returning the same {@code Class} object -- 
and
diff --git 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SimulationCluster.java
 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SimulationCluster.java
index c70c4a8..6af3fc7 100644
--- 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SimulationCluster.java
+++ 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SimulationCluster.java
@@ -27,14 +27,13 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.UUID;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
  * Creates and manages a group of {@link SimulationContext} instances.
  *
  * All contexts share one parent classloader for explicitly-shared and 
default-shared classes.
- * Each context gets its own {@link InstanceClassLoader} for isolated classes.
+ * Each context gets its own IsolatedClassLoader for isolated classes.
  * Contexts are numbered 1..n.
  *
  * @param <C> context type
@@ -45,7 +44,7 @@ public class SimulationCluster<C extends SimulationContext> 
implements AutoClose
     @FunctionalInterface
     public interface ContextFactory<C extends SimulationContext>
     {
-        C create(int id, InstanceClassLoader classLoader, ThreadGroup 
threadGroup);
+        C create(int id, IsolatedClassLoader classLoader, ThreadGroup 
threadGroup);
     }
 
     private final List<C> contexts;
@@ -86,18 +85,11 @@ public class SimulationCluster<C extends SimulationContext> 
implements AutoClose
         Set<String> isolatedNames = classNames(isolatedClasses);
         Set<String> sharedNames   = classNames(sharedClasses);
 
-        // Incorporate the default filter so that explicit isolation always 
wins over
-        // package-prefix defaults.  Order of precedence:
-        //   1. isolatedClasses[] / @Isolated → never shared
-        //   2. sharedClasses[]   / @Shared   → always shared
-        //   3. DEFAULT_SHARED_FILTER         → JVM + framework defaults
-        //   4. anything else                 → isolated (own copy per context)
-        Predicate<String> defaultFilter = 
InstanceClassLoader.getDefaultSharedFilter();
-        Predicate<String> loadShared = name -> {
-            if (isolatedNames.contains(name)) return false;
-            if (sharedNames.contains(name))   return true;
-            return defaultFilter.test(name);
-        };
+        ClassLoadingPolicy policy = 
ClassLoadingPolicy.builder(sharedClassLoader)
+                                                      
.withIsolatedClasses(isolatedNames)
+                                                      
.withSharedClasses(sharedNames)
+                                                      
.withDefaultSharedFilter(IsolatedClassLoader.getDefaultSharedFilter())
+                                                      .build();
 
         List<C> mutable = new ArrayList<>();
         ThreadGroup clusterGroup = new 
ThreadGroup(UUID.randomUUID().toString());
@@ -106,7 +98,7 @@ public class SimulationCluster<C extends SimulationContext> 
implements AutoClose
         {
             int id = i;
             ThreadGroup threadGroup = new ThreadGroup(clusterGroup, "context" 
+ id);
-            InstanceClassLoader cl  = new InstanceClassLoader(1, id, 
classpath, sharedClassLoader, loadShared, null);
+            IsolatedClassLoader cl = new IsolatedClassLoader(1, id, classpath, 
sharedClassLoader, policy, null);
             mutable.add(factory.create(id, cl, threadGroup));
         }
 
@@ -117,7 +109,7 @@ public class SimulationCluster<C extends SimulationContext> 
implements AutoClose
                               .map(SimulationContext::instanceClassLoader)
                               .distinct()
                               .count() == n
-               : "each context must have its own distinct InstanceClassLoader";
+               : "each context must have its own distinct IsolatedClassLoader";
 
         return result;
     }
@@ -179,5 +171,9 @@ public class SimulationCluster<C extends SimulationContext> 
implements AutoClose
         for (Class<?> c : sharedClasses)
             assert c.getAnnotation(Isolated.class) == null
                    : "class " + c.getName() + " is annotated @Isolated but 
listed in sharedClasses[]";
+
+        for (Class<?> c : isolatedClasses)
+            assert c.getAnnotation(Shared.class) == null
+                   : "class " + c.getName() + " is annotated @Shared but 
listed in isolatedClasses[]";
     }
 }
diff --git 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SimulationContext.java
 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SimulationContext.java
index 010fb55..c2ebcc0 100644
--- 
a/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SimulationContext.java
+++ 
b/simulator-context/src/main/java/org/apache/cassandra/simulator/context/SimulationContext.java
@@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit;
 /**
  * A single isolated execution context.
  *
- * Each context owns a dedicated {@link InstanceClassLoader} and a thread pool
+ * Each context owns a dedicated IsolatedClassLoader and a thread pool
  * whose threads have that classloader set as their context classloader (CCL).
  * Code dispatched via {@link #runOn} is serialized into this classloader and
  * executed there; the return value is serialized back so the caller receives
@@ -47,7 +47,7 @@ public class SimulationContext extends IsolatedExecutor
     // app lives inside the isolated CL after startup
     private SimulatedApp app;
 
-    public SimulationContext(int id, InstanceClassLoader classLoader, 
ThreadGroup threadGroup)
+    public SimulationContext(int id, IsolatedClassLoader classLoader, 
ThreadGroup threadGroup)
     {
         super("context" + id, classLoader, createExecutor(id, classLoader, 
threadGroup), DEFAULT_SHUTDOWN_EXECUTOR);
         this.id = id;
@@ -143,9 +143,9 @@ public class SimulationContext extends IsolatedExecutor
 
     // ── Internal 
──────────────────────────────────────────────────────────────
 
-    public InstanceClassLoader instanceClassLoader()
+    public IsolatedClassLoader instanceClassLoader()
     {
-        return (InstanceClassLoader) classLoader;
+        return (IsolatedClassLoader) classLoader;
     }
 
     private static ExecutorService createExecutor(int id, ClassLoader 
classLoader, ThreadGroup threadGroup)
diff --git 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextClassLoaderOwnershipTest.java
 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextClassLoaderOwnershipTest.java
similarity index 77%
rename from 
simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextClassLoaderOwnershipTest.java
rename to 
simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextClassLoaderOwnershipTest.java
index 8907bbd..be3b384 100644
--- 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextClassLoaderOwnershipTest.java
+++ 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextClassLoaderOwnershipTest.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.simulator.tests;
+package org.apache.cassandra.simulator_test;
 
 import org.apache.cassandra.simulator.context.*;
 
@@ -34,9 +34,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  * Guarantee 1: isolated classes are owned by the context's classloader.
  *
  * TC-1.1  Static fields are independent between contexts.
- * TC-1.2  Objects created inside a context report InstanceClassLoader as 
their class's loader.
- * TC-1.3  Each context has a distinct classloader.
- * TC-1.4  The executor thread's CCL matches the instance classloader.
+ * TC-1.2  Host and context see different static state for an isolated class.
+ * TC-1.3  Objects created inside a context report IsolatedClassLoader as 
their class's loader.
+ * TC-1.4  Each context has a distinct classloader.
+ * TC-1.5  The executor thread's CCL matches the instance classloader.
  */
 public class SimulationContextClassLoaderOwnershipTest
 {
@@ -61,6 +62,7 @@ public class SimulationContextClassLoaderOwnershipTest
                                             SimulationContext::new);
         cluster.get(1).startup(TestUtils.NoopApp::new);
         cluster.get(2).startup(TestUtils.NoopApp::new);
+        IsolatedCounter.value = 0;
     }
 
     @AfterEach
@@ -87,19 +89,35 @@ public class SimulationContextClassLoaderOwnershipTest
     // ── TC-1.2 
───────────────────────────────────────────────────────────────
 
     @Test
-    public void objectsCreatedInsideContextReportInstanceClassLoader()
+    public void hostAndContextSeeDifferentStaticState()
+    {
+        IsolatedCounter.value = 11;
+
+        cluster.get(1).runOn(() -> { IsolatedCounter.value = 21; return null; 
});
+        int v1 = cluster.get(1).runOn(() -> IsolatedCounter.value);
+        int v2 = cluster.get(2).runOn(() -> IsolatedCounter.value);
+
+        assertEquals(11, IsolatedCounter.value, "host must keep its own 
isolated static state");
+        assertEquals(21, v1, "context1 must keep its own isolated static 
state");
+        assertEquals(0, v2, "context2 must keep its own isolated static 
state");
+    }
+
+    // ── TC-1.3 
───────────────────────────────────────────────────────────────
+
+    @Test
+    public void objectsCreatedInsideContextReportIsolatedClassLoader()
     {
         String loaderName = cluster.get(1).runOn(() ->
             new IsolatedCounter().getClass().getClassLoader().toString()
         );
 
-        assertTrue(loaderName.contains("InstanceClassLoader"),
-                   "class should be loaded by InstanceClassLoader, got: " + 
loaderName);
+        assertTrue(loaderName.contains("IsolatedClassLoader"),
+                   "class should be loaded by IsolatedClassLoader, got: " + 
loaderName);
         assertTrue(loaderName.contains("id=1"),
                    "loader should identify context 1, got: " + loaderName);
     }
 
-    // ── TC-1.3 
───────────────────────────────────────────────────────────────
+    // ── TC-1.4 
───────────────────────────────────────────────────────────────
 
     @Test
     public void eachContextHasDistinctClassLoader()
@@ -110,10 +128,10 @@ public class SimulationContextClassLoaderOwnershipTest
         assertNotEquals(cl1, cl2, "contexts should have distinct 
classloaders");
     }
 
-    // ── TC-1.4 
───────────────────────────────────────────────────────────────
+    // ── TC-1.5 
───────────────────────────────────────────────────────────────
 
     @Test
-    public void executorThreadCCLMatchesInstanceClassLoader()
+    public void executorThreadCCLMatchesIsolatedClassLoader()
     {
         String threadCL  = cluster.get(1).runOn(() -> 
Thread.currentThread().getContextClassLoader().toString());
         String instanceCL = cluster.get(1).instanceClassLoader().toString();
diff --git 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextClassRoutingTest.java
 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextClassRoutingTest.java
similarity index 97%
rename from 
simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextClassRoutingTest.java
rename to 
simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextClassRoutingTest.java
index ccbd6fe..a11193c 100644
--- 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextClassRoutingTest.java
+++ 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextClassRoutingTest.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.simulator.tests;
+package org.apache.cassandra.simulator_test;
 
 import org.apache.cassandra.simulator.context.*;
 
@@ -77,8 +77,8 @@ public class SimulationContextClassRoutingTest
     {
         String loaderName = cluster.get(1).runOn(() -> 
ForceIsolated.class.getClassLoader().toString());
 
-        assertTrue(loaderName.contains("InstanceClassLoader"),
-                   "ForceIsolated must be loaded by InstanceClassLoader, got: 
" + loaderName);
+        assertTrue(loaderName.contains("IsolatedClassLoader"),
+                   "ForceIsolated must be loaded by IsolatedClassLoader, got: 
" + loaderName);
     }
 
     // ── TC-4.2 
───────────────────────────────────────────────────────────────
diff --git 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextIntegrationTest.java
 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextIntegrationTest.java
similarity index 99%
rename from 
simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextIntegrationTest.java
rename to 
simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextIntegrationTest.java
index a36e5d8..b572014 100644
--- 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextIntegrationTest.java
+++ 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextIntegrationTest.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.simulator.tests;
+package org.apache.cassandra.simulator_test;
 
 import org.apache.cassandra.simulator.context.*;
 
diff --git 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextReturnTransferTest.java
 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextReturnTransferTest.java
similarity index 98%
rename from 
simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextReturnTransferTest.java
rename to 
simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextReturnTransferTest.java
index 9b7bb97..e7c3ffb 100644
--- 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextReturnTransferTest.java
+++ 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextReturnTransferTest.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.simulator.tests;
+package org.apache.cassandra.simulator_test;
 
 import org.apache.cassandra.simulator.context.*;
 
@@ -93,7 +93,7 @@ public class SimulationContextReturnTransferTest
     // ── TC-2.2 
───────────────────────────────────────────────────────────────
 
     @Test
-    public void returnValueClassLoaderIsNotInstanceClassLoader()
+    public void returnValueClassLoaderIsNotIsolatedClassLoader()
     {
         Box result    = cluster.get(1).runOn(() -> new Box(7));
         ClassLoader instanceCL = cluster.get(1).instanceClassLoader();
diff --git 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextSharedClassTest.java
 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextSharedClassTest.java
similarity index 79%
rename from 
simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextSharedClassTest.java
rename to 
simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextSharedClassTest.java
index cccd830..4a038f8 100644
--- 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/SimulationContextSharedClassTest.java
+++ 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/SimulationContextSharedClassTest.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.simulator.tests;
+package org.apache.cassandra.simulator_test;
 
 import org.apache.cassandra.simulator.context.*;
 
@@ -31,15 +31,18 @@ import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
 
 /**
  * Guarantee 3: shared classes are loaded exactly once, with static state 
visible everywhere.
  *
  * TC-3.1  The same Class object is returned from all contexts and the test 
thread.
  * TC-3.2  Shared static state written in one context is readable in another.
- * TC-3.3  The shared class's classloader is the shared CL, not any instance 
CL.
- * TC-3.4  JVM bootstrap classes (e.g. ArrayList) are always shared.
- * TC-3.5  Explicit sharedClasses[] list (no annotation) behaves identically 
to @Shared.
+ * TC-3.3  Shared static state written on the host is readable in a context.
+ * TC-3.4  Shared static state written in a context is readable on the host.
+ * TC-3.5  The shared class's classloader is the shared CL, not any instance 
CL.
+ * TC-3.6  JVM bootstrap classes (e.g. ArrayList) are always shared.
+ * TC-3.7  Explicit sharedClasses[] list (no annotation) behaves identically 
to @Shared.
  */
 public class SimulationContextSharedClassTest
 {
@@ -65,7 +68,7 @@ public class SimulationContextSharedClassTest
         URL[] cp = TestUtils.runtimeClasspath();
         cluster  = SimulationCluster.create(3, cp,
                                             new Class<?>[0],
-                                            new Class<?>[]{ 
SharedCounter.class, ExplicitlyShared.class },
+                                            new Class<?>[]{ 
ExplicitlyShared.class },
                                             SimulationContext::new);
         cluster.get(1).startup(TestUtils.NoopApp::new);
         cluster.get(2).startup(TestUtils.NoopApp::new);
@@ -106,6 +109,32 @@ public class SimulationContextSharedClassTest
 
     // ── TC-3.3 
───────────────────────────────────────────────────────────────
 
+    @Test
+    public void hostWriteIsVisibleInContext()
+    {
+        SharedCounter.value.set(17);
+
+        int v1 = cluster.get(1).runOn(() -> SharedCounter.value.get());
+        int v2 = cluster.get(2).runOn(() -> SharedCounter.value.get());
+
+        assertEquals(17, v1, "host write must be visible in context1");
+        assertEquals(17, v2, "host write must be visible in context2");
+    }
+
+    // ── TC-3.4 
───────────────────────────────────────────────────────────────
+
+    @Test
+    public void contextWriteIsVisibleOnHost()
+    {
+        SharedCounter.value.set(0);
+
+        cluster.get(1).runOn(() -> { SharedCounter.value.set(23); return null; 
});
+
+        assertEquals(23, SharedCounter.value.get(), "context write must be 
visible on the host");
+    }
+
+    // ── TC-3.5 
───────────────────────────────────────────────────────────────
+
     @Test
     public void sharedClassLoaderIsSharedCL()
     {
@@ -122,7 +151,7 @@ public class SimulationContextSharedClassTest
                : "SharedCounter must not be loaded by an instance CL";
     }
 
-    // ── TC-3.4 
───────────────────────────────────────────────────────────────
+    // ── TC-3.6 
───────────────────────────────────────────────────────────────
 
     @Test
     public void jvmBootstrapClassesAreAlwaysShared()
@@ -134,7 +163,7 @@ public class SimulationContextSharedClassTest
         assertNull(ArrayList.class.getClassLoader(), "ArrayList must be 
bootstrap-loaded");
     }
 
-    // ── TC-3.5 
───────────────────────────────────────────────────────────────
+    // ── TC-3.7 
───────────────────────────────────────────────────────────────
 
     @Test
     public void explicitSharedListBehavesLikeAnnotation()
diff --git 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/TestUtils.java
 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/TestUtils.java
similarity index 97%
rename from 
simulator-context/src/test/java/org/apache/cassandra/simulator/tests/TestUtils.java
rename to 
simulator-context/src/test/java/org/apache/cassandra/simulator_test/TestUtils.java
index 5c7ea32..a71d585 100644
--- 
a/simulator-context/src/test/java/org/apache/cassandra/simulator/tests/TestUtils.java
+++ 
b/simulator-context/src/test/java/org/apache/cassandra/simulator_test/TestUtils.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.simulator.tests;
+package org.apache.cassandra.simulator_test;
 
 import org.apache.cassandra.simulator.context.*;
 
diff --git 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/Action.java 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/Action.java
index 00e323d..ad3aa0e 100644
--- a/simulator-core/src/main/java/org/apache/cassandra/simulator/Action.java
+++ b/simulator-core/src/main/java/org/apache/cassandra/simulator/Action.java
@@ -37,7 +37,7 @@ import io.netty.util.internal.DefaultPriorityQueue;
 import io.netty.util.internal.PriorityQueue;
 import io.netty.util.internal.PriorityQueueNode;
 
-import static 
org.apache.cassandra.config.SimulatorProperties.TEST_SIMULATOR_DEBUG;
+import static 
org.apache.cassandra.simulator.SimulatorProperties.TEST_SIMULATOR_DEBUG;
 import static org.apache.cassandra.simulator.Action.Modifier.DAEMON;
 import static org.apache.cassandra.simulator.Action.Modifier.ORPHAN;
 import static 
org.apache.cassandra.simulator.Action.Modifier.ORPHAN_TO_GRANDPARENT;
diff --git 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/ActionSchedule.java
 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/ActionSchedule.java
index 6f9aabf..72bd411 100644
--- 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/ActionSchedule.java
+++ 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/ActionSchedule.java
@@ -45,7 +45,7 @@ import org.apache.cassandra.simulator.utils.Throwables;
 import io.netty.util.internal.DefaultPriorityQueue;
 import io.netty.util.internal.PriorityQueue;
 
-import static 
org.apache.cassandra.config.SimulatorProperties.TEST_SIMULATOR_DEBUG;
+import static 
org.apache.cassandra.simulator.SimulatorProperties.TEST_SIMULATOR_DEBUG;
 import static org.apache.cassandra.simulator.Action.Modifier.DAEMON;
 import static org.apache.cassandra.simulator.Action.Modifier.STREAM;
 import static org.apache.cassandra.simulator.Action.Phase.CONSEQUENCE;
diff --git 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/Ordered.java 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/Ordered.java
index 754ae6f..9b3ebf5 100644
--- a/simulator-core/src/main/java/org/apache/cassandra/simulator/Ordered.java
+++ b/simulator-core/src/main/java/org/apache/cassandra/simulator/Ordered.java
@@ -33,7 +33,7 @@ import 
org.apache.cassandra.simulator.utils.IntrusiveLinkedList;
 import org.apache.cassandra.simulator.utils.IntrusiveLinkedListNode;
 
 import static java.util.Collections.newSetFromMap;
-import static 
org.apache.cassandra.config.SimulatorProperties.TEST_SIMULATOR_DEBUG;
+import static 
org.apache.cassandra.simulator.SimulatorProperties.TEST_SIMULATOR_DEBUG;
 
 /**
  * Represents an action that may not run before certain other actions
diff --git 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/Simulator.java 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/Simulator.java
index b90facc..feb5d6a 100644
--- a/simulator-core/src/main/java/org/apache/cassandra/simulator/Simulator.java
+++ b/simulator-core/src/main/java/org/apache/cassandra/simulator/Simulator.java
@@ -39,9 +39,10 @@ import java.util.function.Supplier;
 
 import org.apache.cassandra.simulator.asm.InterceptAsClassTransformer;
 import org.apache.cassandra.simulator.asm.InterceptRule;
+import org.apache.cassandra.simulator.context.ClassLoadingPolicy;
 import org.apache.cassandra.simulator.context.IClassTransformer;
 import org.apache.cassandra.simulator.context.IIsolatedExecutor;
-import org.apache.cassandra.simulator.context.InstanceClassLoader;
+import org.apache.cassandra.simulator.context.IsolatedClassLoader;
 import org.apache.cassandra.simulator.context.IsolatedExecutor;
 import org.apache.cassandra.simulator.step.Session;
 import org.apache.cassandra.simulator.systems.InterceptedExecution;
@@ -420,7 +421,7 @@ public class Simulator implements AutoCloseable
 
     /**
      * Run the simulation by transferring each body into an ASM-transformed
-     * {@link InstanceClassLoader}, wrapping each in a simulated thread, and 
executing
+     * {@link IsolatedClassLoader}, wrapping each in a simulated thread, and 
executing
      * them under scheduler control.
      *
      * <p>Inside the bodies, standard Java concurrency primitives are 
transparently
@@ -485,7 +486,7 @@ public class Simulator implements AutoCloseable
     /**
      * Prepare a simulation for step-by-step execution.
      *
-     * <p>The bodies are serialized into an {@link InstanceClassLoader}, 
wrapped in
+     * <p>The bodies are serialized into an {@link IsolatedClassLoader}, 
wrapped in
      * simulated thread actions, and queued in an {@link ActionSchedule} - but 
nothing
      * executes until the caller drives the returned {@link Session}.
      *
@@ -547,7 +548,7 @@ public class Simulator implements AutoCloseable
      * objects are instances of the simulation classloader's version of the 
impl class,
      * matching the CHECKCAST target in the transformed bytecode.
      */
-    private void registerDeferredFactories(InstanceClassLoader instanceCL)
+    private void registerDeferredFactories(IsolatedClassLoader instanceCL)
     {
         for (DeferredReg reg : deferredRegistrations)
         {
@@ -595,16 +596,19 @@ public class Simulator implements AutoCloseable
             Collections.unmodifiableSet(new LinkedHashSet<>(customRules))
         );
 
-        Predicate<String> loadShared = name ->
-            InstanceClassLoader.getDefaultSharedFilter().test(name)
-            || (name.startsWith("org.apache.cassandra.simulator.")
-                && !name.startsWith("org.apache.cassandra.simulator.test."))
-            || name.startsWith("org.apache.cassandra.utils.");
+        ClassLoader sharedClassLoader = ClassLoader.getSystemClassLoader();
+        ClassLoadingPolicy policy = 
ClassLoadingPolicy.builder(sharedClassLoader)
+                                                      
.withDefaultSharedFilter(name ->
+                                                          
IsolatedClassLoader.getDefaultSharedFilter().test(name)
+                                                          || 
(name.startsWith("org.apache.cassandra.simulator.")
+                                                              && 
!name.startsWith("org.apache.cassandra.simulator.test."))
+                                                          || 
name.startsWith("org.apache.cassandra.utils."))
+                                                      .build();
 
-        InstanceClassLoader instanceCL = new InstanceClassLoader(
+        IsolatedClassLoader instanceCL = new IsolatedClassLoader(
             0, 1, urls,
-            ClassLoader.getSystemClassLoader(),
-            loadShared,
+            sharedClassLoader,
+            policy,
             transformer
         );
 
@@ -628,14 +632,12 @@ public class Simulator implements AutoCloseable
             actions.add(threadAction("thread-" + i, transferred));
         }
 
-        return new ActionSchedule(
-            simulatedTime,
-            () -> 0L,
-            () -> 0L,
-            new RunnableActionScheduler.Sequential(),
-            new ActionSchedule.Work(ActionSchedule.Mode.FINITE,
-                                    
Collections.singletonList(ActionList.of(actions)))
-        );
+        return new ActionSchedule(simulatedTime,
+                                  () -> 0L,
+                                  () -> 0L,
+                                  new RunnableActionScheduler.Sequential(),
+                                  new 
ActionSchedule.Work(ActionSchedule.Mode.FINITE,
+                                                          
Collections.singletonList(ActionList.of(actions))));
     }
 
     /**
diff --git 
a/simulator-core/src/main/java/org/apache/cassandra/config/SimulatorProperties.java
 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/SimulatorProperties.java
similarity index 97%
rename from 
simulator-core/src/main/java/org/apache/cassandra/config/SimulatorProperties.java
rename to 
simulator-core/src/main/java/org/apache/cassandra/simulator/SimulatorProperties.java
index 153fb30..dfd23ee 100644
--- 
a/simulator-core/src/main/java/org/apache/cassandra/config/SimulatorProperties.java
+++ 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/SimulatorProperties.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.config;
+package org.apache.cassandra.simulator;
 
 import java.util.function.Function;
 
diff --git 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingExecutor.java
 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingExecutor.java
index 2d13db0..cbde83f 100644
--- 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingExecutor.java
+++ 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingExecutor.java
@@ -53,7 +53,7 @@ import static java.util.Collections.newSetFromMap;
 import static java.util.Collections.synchronizedMap;
 import static java.util.Collections.synchronizedSet;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
-import static 
org.apache.cassandra.config.SimulatorProperties.TEST_SIMULATOR_DEBUG;
+import static 
org.apache.cassandra.simulator.SimulatorProperties.TEST_SIMULATOR_DEBUG;
 import static 
org.apache.cassandra.simulator.systems.InterceptibleThread.runDeterministic;
 import static 
org.apache.cassandra.simulator.systems.SimulatedAction.Kind.SCHEDULED_DAEMON;
 import static 
org.apache.cassandra.simulator.systems.SimulatedAction.Kind.SCHEDULED_TASK;
diff --git 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingGlobalMethods.java
 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingGlobalMethods.java
index a09ddcc..c98e5c5 100644
--- 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingGlobalMethods.java
+++ 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingGlobalMethods.java
@@ -27,15 +27,12 @@ import java.util.function.LongConsumer;
 
 import javax.annotation.Nullable;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.apache.cassandra.simulator.RandomSource;
 import 
org.apache.cassandra.simulator.systems.InterceptedWait.CaptureSites.Capture;
 import 
org.apache.cassandra.simulator.systems.InterceptedWait.InterceptedConditionWait;
 import org.apache.cassandra.simulator.utils.Clock;
 
-import static 
org.apache.cassandra.config.SimulatorProperties.TEST_SIMULATOR_DETERMINISM_CHECK;
+import static 
org.apache.cassandra.simulator.SimulatorProperties.TEST_SIMULATOR_DETERMINISM_CHECK;
 import static org.apache.cassandra.simulator.SimulatorUtils.failWithOOM;
 import static 
org.apache.cassandra.simulator.systems.InterceptedWait.Kind.NEMESIS;
 import static 
org.apache.cassandra.simulator.systems.NonInterceptible.Permit.OPTIONAL;
diff --git 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingMonitors.java
 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingMonitors.java
index 6626c8e..9a73d8f 100644
--- 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingMonitors.java
+++ 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/systems/InterceptingMonitors.java
@@ -37,7 +37,7 @@ import org.apache.cassandra.simulator.utils.Threads;
 import org.apache.cassandra.simulator.utils.UncheckedInterruptedException;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static 
org.apache.cassandra.config.SimulatorProperties.TEST_SIMULATOR_DEBUG;
+import static 
org.apache.cassandra.simulator.SimulatorProperties.TEST_SIMULATOR_DEBUG;
 import static org.apache.cassandra.simulator.SimulatorUtils.failWithOOM;
 import static 
org.apache.cassandra.simulator.systems.InterceptedWait.Kind.NEMESIS;
 import static 
org.apache.cassandra.simulator.systems.InterceptedWait.Kind.SLEEP_UNTIL;
diff --git 
a/simulator-core/src/test/java/org/apache/cassandra/simulator_test/ParkUnparkTest.java
 
b/simulator-core/src/test/java/org/apache/cassandra/simulator_test/ParkUnparkTest.java
index beaadfc..3f0e166 100644
--- 
a/simulator-core/src/test/java/org/apache/cassandra/simulator_test/ParkUnparkTest.java
+++ 
b/simulator-core/src/test/java/org/apache/cassandra/simulator_test/ParkUnparkTest.java
@@ -18,30 +18,53 @@
 
 package org.apache.cassandra.simulator_test;
 
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.locks.LockSupport;
 
 import org.apache.cassandra.simulator.Simulator;
+import org.apache.cassandra.simulator.step.Session;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import 
org.apache.cassandra.simulator.context.IIsolatedExecutor.SerializableRunnable;
+import org.apache.cassandra.simulator.context.Shared;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
- * Verifies that {@link LockSupport#park()} and {@link 
LockSupport#unpark(Thread)} are
- * intercepted by the ASM transformer and work correctly under simulation.
+ * Verifies that LockSupport.park() and LockSupport.unpark(Thread) are 
intercepted and work
+ * correctly under simulation.
  *
- * <p>Important: when {@code new Thread(...).start()} is ASM-rewritten to
- * {@link 
org.apache.cassandra.simulator.systems.InterceptorOfGlobalMethods.Global#startThread},
- * the original {@code Thread} object is <em>not</em> the running thread — the 
simulator
- * creates a new {@code InterceptibleThread} to run the body.  {@code 
LockSupport.unpark}
- * must be called with the <em>running</em> thread reference, so we capture
- * {@code Thread.currentThread()} from inside the parker body and share it 
with the
- * unparker via a {@code CountDownLatch}-guarded reference slot.
+ * Important: after ASM rewrites new Thread(...).start(), the original Thread 
object is not the
+ * running thread. The simulator creates an InterceptibleThread to run the 
body instead.
+ *
+ * LockSupport.unpark() must therefore receive the running thread reference 
captured from inside
+ * the parker body via Thread.currentThread().
  */
 public class ParkUnparkTest
 {
+    @Shared
+    public static class SharedState
+    {
+        public static final Set<String> events = 
Collections.synchronizedSet(new LinkedHashSet<>());
+
+        public static void reset()
+        {
+            events.clear();
+        }
+    }
+
+    @BeforeEach
+    void reset()
+    {
+        SharedState.reset();
+    }
+
     @Test
     void unparkWakesParkedThread()
     {
@@ -49,24 +72,83 @@ public class ParkUnparkTest
             int[] result = { 0 };
             CountDownLatch parkerReady = new CountDownLatch(1);
             CountDownLatch done = new CountDownLatch(1);
-            // Slot for the *running* InterceptibleThread — set from inside 
the parker body.
             Thread[] parkerThread = { null };
 
             new Thread(() -> {
-                parkerThread[0] = Thread.currentThread(); // the 
InterceptibleThread
-                parkerReady.countDown();                   // signal: ref is 
ready
+                parkerThread[0] = Thread.currentThread();
+                parkerReady.countDown();
                 LockSupport.park();
                 result[0] = 1;
                 done.countDown();
             }, "parker").start();
 
             new Thread(() -> {
-                try { parkerReady.await(); } catch (InterruptedException e) { 
return; }
-                LockSupport.unpark(parkerThread[0]);       // wake the 
InterceptibleThread
+                try { parkerReady.await(); }
+                catch (InterruptedException e)
+                {
+                    Thread.currentThread().interrupt();
+                    return;
+                }
+                LockSupport.unpark(parkerThread[0]);
             }, "unparker").start();
 
-            try { done.await(); } catch (InterruptedException e) { 
Thread.currentThread().interrupt(); }
+            try { done.await(); }
+            catch (InterruptedException e) { 
Thread.currentThread().interrupt(); }
             assertEquals(1, result[0], "parked thread should have been woken 
by unpark");
         });
     }
+
+    @Test
+    void unparkWakesParkedThread_byStep()
+    {
+        try (Simulator sim = new Simulator(42L);
+             Session session = sim.byStep((SerializableRunnable) () -> {
+                 CountDownLatch parkerReady = new CountDownLatch(1);
+                 CountDownLatch done = new CountDownLatch(2);
+                 Thread[] parkedThread = { null };
+
+                 new Thread(() -> {
+                     parkedThread[0] = Thread.currentThread();
+                     parkerReady.countDown();
+                     SharedState.events.add("parking");
+                     LockSupport.park();
+                     SharedState.events.add("unparked");
+                     done.countDown();
+                 }, "parker").start();
+
+                 new Thread(() -> {
+                     try { parkerReady.await(); }
+                     catch (InterruptedException e)
+                     {
+                         Thread.currentThread().interrupt();
+                         return;
+                     }
+                     LockSupport.unpark(parkedThread[0]);
+                     done.countDown();
+                 }, "unparker").start();
+
+                 try { done.await(); }
+                 catch (InterruptedException e) { 
Thread.currentThread().interrupt(); }
+             }))
+        {
+            assertEquals(Set.of(), SharedState.events);
+
+            assertTrue(session.step());
+            assertEquals(Set.of(), SharedState.events);
+
+            assertTrue(session.step());
+            assertEquals(Set.of("parking"), SharedState.events, "parker 
reached park() and is suspended");
+
+            assertTrue(session.step());
+            assertEquals(Set.of("parking"), SharedState.events, "unparker ran, 
but wakeup has not fired yet");
+
+            assertTrue(session.step());
+            assertEquals(Set.of("parking", "unparked"), SharedState.events, 
"parker resumed after unpark");
+
+            assertTrue(session.step());
+            assertEquals(Set.of("parking", "unparked"), SharedState.events);
+            assertFalse(session.hasNext());
+        }
+    }
+
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to