RFR: 8343793: Test java/foreign/TestMemorySession.java is timing out

Maurizio Cimadamore mcimadamore at openjdk.org
Fri Nov 8 12:26:37 UTC 2024


On Fri, 8 Nov 2024 11:56:08 GMT, Quan Anh Mai <qamai 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.
> 
> 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 direct:

* both thread agree on an iteration count (the atomic integer)
* thread A keeps trying closing a scope -- if it succeds, its iteration is done, and it waits for the next thread to be done
* thread B keeps acquiring and releasing a scope -- if it fails to acquire, that means that the scope is closed by the other thread, so this iteration is done, and we can update the shared iteration counter

What do you think?

-------------

PR Comment: https://git.openjdk.org/jdk/pull/21976#issuecomment-2464628207


More information about the core-libs-dev mailing list