The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: Alice Ryhl <aliceryhl@google.com>
To: Bo Zhang <zhangbo0325@gmail.com>
Cc: gregkh@linuxfoundation.org, cmllamas@google.com,
	arve@android.com,  tkjos@android.com, christian@brauner.io,
	surenb@google.com, baohua@kernel.org,  zhanghongru06@gmail.com,
	linux-kernel@vger.kernel.org
Subject: Re: [RFC PATCH 0/1] binder: switch alloc->mutex back to spinlock
Date: Sat, 8 Aug 2026 13:49:46 +0000	[thread overview]
Message-ID: <ancz-vd9v5P5kRJq@google.com> (raw)
In-Reply-To: <20260806142301.2144536-1-zhangbo56@xiaomi.com>

On Thu, Aug 06, 2026 at 10:23:01PM +0800, Bo Zhang wrote:
> On Thu, Aug 06, 2026 at 09:01:19AM +0000, Alice Ryhl wrote:
> > This will not work due to the following race condition between T1 and T2:
> > 
> > T1 binder_alloc_free_page() removes the page from alloc->pages
> > T2 binder_alloc_new_buf_locked() runs
> > T2 binder_install_single_page() gets -EBUSY
> > T2 binder_page_lookup() looks up the page in the vma
> > T1 binder_alloc_free_page() calls zap_vma_range()
> > 
> > In this scenario, T2 looks up a page that it expects to be "pinned" and
> > not removable by the shrinker, but the shrinker zaps it anyway.
> > 
> > Today this cannot happen because binder_alloc_new_buf_locked() runs
> > under the mutex, which ensures that zap_vma_range() finishes removing
> > the page before binder_install_single_page() runs.
> 
> Hi Alice,
> 
> Thanks for pointing out this race. You're right that simply moving
> spin_unlock() before zap_vma_range() opens a window where the install
> side could GUP the old page that is about to be freed.
> 
> However, I think it could be resolved without keeping the lock across
> zap_vma_range(). The solution might be:
> 
> The install side only calls vm_insert_page() when pages[index] == NULL.
> And the shrinker sets pages[index] = NULL before zapping. So if
> vm_insert_page() returns -EBUSY, we can check pages[index] to determine
> which scenario we're in:
> 
>   1. pages[index] != NULL: Another installer won the race. It's safe to
>      GUP the page, which is the normal concurrent install case.
> 
>   2. pages[index] == NULL: The shrinker cleared it but hasn't zapped
>      the PTE yet. We must NOT GUP the old page. Return -EAGAIN and
>      let the caller retry after the shrinker finishes.
> 
> Case 2 is safe because: the install side only attempts installation
> when pages[index] == NULL, so if EBUSY occurs with pages[index] == NULL,
> it must be a PTE left over from the page that the shrinker is currently
> reclaiming. After the shrinker zaps and frees, the retry will find
> pages[index] still NULL, call vm_insert_page() again, and succeed since
> the PTE is now clear.
> 
> Case 1 cannot race with the shrinker because: if pages[index] != NULL
> at the time of the EBUSY check, the page was just installed by another
> thread and is not on the LRU freelist, so the shrinker won't touch it.
> 
> The change would look like:
> 
>   case -EBUSY:
>       binder_free_page(page);
>       if (!binder_get_installed_page(alloc, index)) {
>           /* shrinker is reclaiming, retry */
>           ret = -EAGAIN;
>           break;
>       }
>       /* normal concurrent install, look up the winner's page */
>       page = binder_page_lookup(alloc, addr);
>       ...
> 
> And the caller retries on -EAGAIN with a cond_resched().
> 
> This also should not introduce any performance regression since the
> -EAGAIN retry only triggers in the rare case where EBUSY coincides with
> an active shrinker reclaim on the same page index, a window that lasts
> only for the duration of a single zap_vma_range() call.
> 
> Does this approach look reasonable to you?

No, this will deadlock if the thread installing the page is SCHED_FIFO.

Waiting for the shrinker by repeatedly checking whether the shrinker is
done in a loop does not work.

As an aside, your solution also adds similar waiting for concurrent
installers. If the successful installer gets preempted between
binder_page_insert() and binder_set_installed_page(), then your code
will also repeatedly retry until said thread gets scheduled in again. So
there is a similar deadlock if the inserter that failed the race is
SCHED_FIFO.

Alice

      reply	other threads:[~2026-08-08 13:49 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-05 15:27 [RFC PATCH 0/1] binder: switch alloc->mutex back to spinlock Bo Zhang
2026-08-05 15:27 ` [RFC PATCH 1/1] " Bo Zhang
2026-08-06  9:01 ` [RFC PATCH 0/1] " Alice Ryhl
2026-08-06 14:23   ` Bo Zhang
2026-08-08 13:49     ` Alice Ryhl [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ancz-vd9v5P5kRJq@google.com \
    --to=aliceryhl@google.com \
    --cc=arve@android.com \
    --cc=baohua@kernel.org \
    --cc=christian@brauner.io \
    --cc=cmllamas@google.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=surenb@google.com \
    --cc=tkjos@android.com \
    --cc=zhangbo0325@gmail.com \
    --cc=zhanghongru06@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox