RFR: 8343793: Test java/foreign/TestMemorySession.java is timing out
Maurizio Cimadamore
mcimadamore at openjdk.org
Fri Nov 8 15:58:10 UTC 2024
On Fri, 8 Nov 2024 12:22:54 GMT, Maurizio Cimadamore <mcimadamore at openjdk.org> wrote:
>> Hi,
>>
>> This patch fixes the deadlock in `TestMemorySession#testAcquireCloseRace`. The lock-step spin lock is implemented as with `lock` being an `AtomicInteger`:
>>
>> // Keep the 2 threads operating on the same scope
>> int k = lock.getAndAdd(1) + 1;
>> while (k != i * 2) {
>> Thread.onSpinWait();
>> k = lock.get();
>> }
>>
>> Given the initial condition:
>>
>> Thread 1: i = 0
>> Thread 2: i = 0
>> lock: -2
>>
>> The `lock` then undergoes the following operations:
>>
>>
>>
>> Thread 1 Thread 2 lock value
>> getAndAdd(1) -1
>> getAndAdd(1) 0 -> Thread 2 then continues its next iteration, its i value is now 1
>> getAndAdd(1) 1 -> Thread 2 reaches the next iteration before thread 1 has a chance to read the value 0
>> get() 1 -> Thread 1 now cannot proceed because it missed the value 0
>> get() 1 -> Thread 2 now cannot proceed because lock can never reach 2
>>
>>
>> The solution is to not rely on the exact value of the lock but instead whether the lock has passed the expected value.
>>
>> Testing: I have run this test several hundreds times and got no failure while without this patch I encountered a timeout every approximately 30 times.
>>
>> Please take a look, thanks a lot.
>
> I'm playing with something like this:
>
>
> @Test
> public void testAcquireCloseRace() throws InterruptedException {
> int MAX_ITERATIONS = 1000;
> AtomicInteger iteration = new AtomicInteger();
> boolean[] result = new boolean[1];
> MemorySessionImpl[] scopes = new MemorySessionImpl[MAX_ITERATIONS];
> for (int i = 0; i < MAX_ITERATIONS; i++) {
> scopes[i] = MemorySessionImpl.toMemorySession(Arena.ofShared());
> }
>
> // This thread tries to close the scopes
> Thread t1 = new Thread(() -> {
> int it = iteration.get();
> while (it < MAX_ITERATIONS) {
> while (true) {
> try {
> scopes[it].close();
> // if we close successfully, the iteration is now completed
> break;
> } catch (IllegalStateException e) {
> // scope is acquired, wait and try again
> Thread.onSpinWait();
> }
> }
> // wait for other thread to complete its iteration
> int prev = it;
> while (prev == it) {
> Thread.onSpinWait();
> it = iteration.get();
> }
> }
> });
>
> // This thread tries to acquire the scopes, then check if it is alive after an acquire failure
> Thread t2 = new Thread(() -> {
> int it = iteration.get();
> while (it < MAX_ITERATIONS) {
> while (true) {
> try {
> scopes[it].acquire0();
> } catch (IllegalStateException e) {
> // we failed to acquire, meaning the other thread has closed this scope
> if (scopes[it].isAlive()) {
> result[0] = true;
> }
> // this iteration is now completed
> break;
> }
> // if we get here, acquire was successful, so we need to release...
> scopes[it].release0();
> // ... and then try again
> }
> // advance to next iteration
> it = iteration.addAndGet(1);
> }
> });
>
> t1.start();
> t2.start();
> t1.join();
> t2.join();
> assertFalse(result[0]);
> }
>
>
> I think I find this logic a bit more dir...
> @mcimadamore I think your approach is more or less similar. Although it is a little bit clearer in terms of what index we are operating on, it is a bit less clear in terms of the synchronization mechanism, as it is spread out to a wider scope.
IMHO both synchronization mechanisms are spread out in scope, as no matter what you'll have an action at a distance. FWIW, I find the logic with index updating particularly hard to read -- and a very indirect way to coordinate the two threads, which is why I tried to reach for something a little more explicit.
-------------
PR Comment: https://git.openjdk.org/jdk/pull/21976#issuecomment-2465099538
More information about the core-libs-dev
mailing list