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

Reply via email to