Message ID | 20210110124017.86750-4-songmuchun@bytedance.com |
---|---|
State | Superseded |
Headers | show |
Series | None | expand |
On 1/10/21 4:40 AM, Muchun Song wrote: > There is a race condition between __free_huge_page() > and dissolve_free_huge_page(). > > CPU0: CPU1: > > // page_count(page) == 1 > put_page(page) > __free_huge_page(page) > dissolve_free_huge_page(page) > spin_lock(&hugetlb_lock) > // PageHuge(page) && !page_count(page) > update_and_free_page(page) > // page is freed to the buddy > spin_unlock(&hugetlb_lock) > spin_lock(&hugetlb_lock) > clear_page_huge_active(page) > enqueue_huge_page(page) > // It is wrong, the page is already freed > spin_unlock(&hugetlb_lock) > > The race windows is between put_page() and dissolve_free_huge_page(). > > We should make sure that the page is already on the free list > when it is dissolved. > > Fixes: c8721bbbdd36 ("mm: memory-hotplug: enable memory hotplug to handle hugepage") > Signed-off-by: Muchun Song <songmuchun@bytedance.com> > Cc: stable@vger.kernel.org > --- > mm/hugetlb.c | 26 ++++++++++++++++++++++++++ > 1 file changed, 26 insertions(+) Thanks, It is unfortunate that we have to add more huge page state information to fix this issue. However, I believe we have explored all other options. Reviewed-by: Mike Kravetz <mike.kravetz@oracle.com> -- Mike Kravetz
On Sun 10-01-21 20:40:14, Muchun Song wrote: [...] > @@ -1770,6 +1788,14 @@ int dissolve_free_huge_page(struct page *page) > int nid = page_to_nid(head); > if (h->free_huge_pages - h->resv_huge_pages == 0) > goto out; > + > + /* > + * We should make sure that the page is already on the free list > + * when it is dissolved. > + */ > + if (unlikely(!PageHugeFreed(head))) > + goto out; > + Do you really want to report EBUSY in this case? This doesn't make much sense to me TBH. I believe you want to return 0 same as when you race and the page is no longer PageHuge. > /* > * Move PageHWPoison flag from head page to the raw error page, > * which makes any subpages rather than the error page reusable. > -- > 2.11.0 -- Michal Hocko SUSE Labs
On Tue, Jan 12, 2021 at 6:02 PM Michal Hocko <mhocko@suse.com> wrote: > > On Sun 10-01-21 20:40:14, Muchun Song wrote: > [...] > > @@ -1770,6 +1788,14 @@ int dissolve_free_huge_page(struct page *page) > > int nid = page_to_nid(head); > > if (h->free_huge_pages - h->resv_huge_pages == 0) > > goto out; > > + > > + /* > > + * We should make sure that the page is already on the free list > > + * when it is dissolved. > > + */ > > + if (unlikely(!PageHugeFreed(head))) > > + goto out; > > + > > Do you really want to report EBUSY in this case? This doesn't make much > sense to me TBH. I believe you want to return 0 same as when you race > and the page is no longer PageHuge. Return 0 is wrong. Because the page is not freed to the buddy allocator. IIUC, dissolve_free_huge_page returns 0 when the page is already freed to the buddy allocator. Right? > > > /* > > * Move PageHWPoison flag from head page to the raw error page, > > * which makes any subpages rather than the error page reusable. > > -- > > 2.11.0 > > -- > Michal Hocko > SUSE Labs
On Tue 12-01-21 18:13:02, Muchun Song wrote: > On Tue, Jan 12, 2021 at 6:02 PM Michal Hocko <mhocko@suse.com> wrote: > > > > On Sun 10-01-21 20:40:14, Muchun Song wrote: > > [...] > > > @@ -1770,6 +1788,14 @@ int dissolve_free_huge_page(struct page *page) > > > int nid = page_to_nid(head); > > > if (h->free_huge_pages - h->resv_huge_pages == 0) > > > goto out; > > > + > > > + /* > > > + * We should make sure that the page is already on the free list > > > + * when it is dissolved. > > > + */ > > > + if (unlikely(!PageHugeFreed(head))) > > > + goto out; > > > + > > > > Do you really want to report EBUSY in this case? This doesn't make much > > sense to me TBH. I believe you want to return 0 same as when you race > > and the page is no longer PageHuge. > > Return 0 is wrong. Because the page is not freed to the buddy allocator. > IIUC, dissolve_free_huge_page returns 0 when the page is already freed > to the buddy allocator. Right? 0 is return when the page is either dissolved or it doesn't need dissolving. If there is a race with somebody else freeing the page then there is nothing to dissolve. Under which condition it makes sense to report the failure and/or retry dissolving? -- Michal Hocko SUSE Labs
On Tue, Jan 12, 2021 at 7:17 PM Michal Hocko <mhocko@suse.com> wrote: > > On Tue 12-01-21 18:13:02, Muchun Song wrote: > > On Tue, Jan 12, 2021 at 6:02 PM Michal Hocko <mhocko@suse.com> wrote: > > > > > > On Sun 10-01-21 20:40:14, Muchun Song wrote: > > > [...] > > > > @@ -1770,6 +1788,14 @@ int dissolve_free_huge_page(struct page *page) > > > > int nid = page_to_nid(head); > > > > if (h->free_huge_pages - h->resv_huge_pages == 0) > > > > goto out; > > > > + > > > > + /* > > > > + * We should make sure that the page is already on the free list > > > > + * when it is dissolved. > > > > + */ > > > > + if (unlikely(!PageHugeFreed(head))) > > > > + goto out; > > > > + > > > > > > Do you really want to report EBUSY in this case? This doesn't make much > > > sense to me TBH. I believe you want to return 0 same as when you race > > > and the page is no longer PageHuge. > > > > Return 0 is wrong. Because the page is not freed to the buddy allocator. > > IIUC, dissolve_free_huge_page returns 0 when the page is already freed > > to the buddy allocator. Right? > > 0 is return when the page is either dissolved or it doesn't need > dissolving. If there is a race with somebody else freeing the page then > there is nothing to dissolve. Under which condition it makes sense to > report the failure and/or retry dissolving? If there is a race with somebody else freeing the page, the page can be freed to the hugepage pool not the buddy allocator. Do you think that this page is dissolved? > -- > Michal Hocko > SUSE Labs
On Tue 12-01-21 19:43:21, Muchun Song wrote: > On Tue, Jan 12, 2021 at 7:17 PM Michal Hocko <mhocko@suse.com> wrote: > > > > On Tue 12-01-21 18:13:02, Muchun Song wrote: > > > On Tue, Jan 12, 2021 at 6:02 PM Michal Hocko <mhocko@suse.com> wrote: > > > > > > > > On Sun 10-01-21 20:40:14, Muchun Song wrote: > > > > [...] > > > > > @@ -1770,6 +1788,14 @@ int dissolve_free_huge_page(struct page *page) > > > > > int nid = page_to_nid(head); > > > > > if (h->free_huge_pages - h->resv_huge_pages == 0) > > > > > goto out; > > > > > + > > > > > + /* > > > > > + * We should make sure that the page is already on the free list > > > > > + * when it is dissolved. > > > > > + */ > > > > > + if (unlikely(!PageHugeFreed(head))) > > > > > + goto out; > > > > > + > > > > > > > > Do you really want to report EBUSY in this case? This doesn't make much > > > > sense to me TBH. I believe you want to return 0 same as when you race > > > > and the page is no longer PageHuge. > > > > > > Return 0 is wrong. Because the page is not freed to the buddy allocator. > > > IIUC, dissolve_free_huge_page returns 0 when the page is already freed > > > to the buddy allocator. Right? > > > > 0 is return when the page is either dissolved or it doesn't need > > dissolving. If there is a race with somebody else freeing the page then > > there is nothing to dissolve. Under which condition it makes sense to > > report the failure and/or retry dissolving? > > If there is a race with somebody else freeing the page, the page > can be freed to the hugepage pool not the buddy allocator. Do > you think that this page is dissolved? OK, I see what you mean. Effectively the page would be in a limbo, not yet in the pool nor in the allocator but it can find its way to the either of the two. But I still dislike returning a failure because that would mean e.g. memory hotplug to fail. Can you simply retry inside this code path (drop the lock, cond_resched and retry)? -- Michal Hocko SUSE Labs
On Tue, Jan 12, 2021 at 8:37 PM Michal Hocko <mhocko@suse.com> wrote: > > On Tue 12-01-21 19:43:21, Muchun Song wrote: > > On Tue, Jan 12, 2021 at 7:17 PM Michal Hocko <mhocko@suse.com> wrote: > > > > > > On Tue 12-01-21 18:13:02, Muchun Song wrote: > > > > On Tue, Jan 12, 2021 at 6:02 PM Michal Hocko <mhocko@suse.com> wrote: > > > > > > > > > > On Sun 10-01-21 20:40:14, Muchun Song wrote: > > > > > [...] > > > > > > @@ -1770,6 +1788,14 @@ int dissolve_free_huge_page(struct page *page) > > > > > > int nid = page_to_nid(head); > > > > > > if (h->free_huge_pages - h->resv_huge_pages == 0) > > > > > > goto out; > > > > > > + > > > > > > + /* > > > > > > + * We should make sure that the page is already on the free list > > > > > > + * when it is dissolved. > > > > > > + */ > > > > > > + if (unlikely(!PageHugeFreed(head))) > > > > > > + goto out; > > > > > > + > > > > > > > > > > Do you really want to report EBUSY in this case? This doesn't make much > > > > > sense to me TBH. I believe you want to return 0 same as when you race > > > > > and the page is no longer PageHuge. > > > > > > > > Return 0 is wrong. Because the page is not freed to the buddy allocator. > > > > IIUC, dissolve_free_huge_page returns 0 when the page is already freed > > > > to the buddy allocator. Right? > > > > > > 0 is return when the page is either dissolved or it doesn't need > > > dissolving. If there is a race with somebody else freeing the page then > > > there is nothing to dissolve. Under which condition it makes sense to > > > report the failure and/or retry dissolving? > > > > If there is a race with somebody else freeing the page, the page > > can be freed to the hugepage pool not the buddy allocator. Do > > you think that this page is dissolved? > > OK, I see what you mean. Effectively the page would be in a limbo, not > yet in the pool nor in the allocator but it can find its way to the > either of the two. But I still dislike returning a failure because that > would mean e.g. memory hotplug to fail. Can you simply retry inside this > code path (drop the lock, cond_resched and retry)? Yeah. This is what I want to do (making the memory hotplug as successful as possible). So I send the patch: [PATCH v3 4/6] mm: hugetlb: add return -EAGAIN for dissolve_free_huge_page Adding a simple retry inside this function when hitting this race is also fine to me. I can do that. > -- > Michal Hocko > SUSE Labs
diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 4741d60f8955..4a9011e12175 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -79,6 +79,21 @@ DEFINE_SPINLOCK(hugetlb_lock); static int num_fault_mutexes; struct mutex *hugetlb_fault_mutex_table ____cacheline_aligned_in_smp; +static inline bool PageHugeFreed(struct page *head) +{ + return page_private(head + 4) == -1UL; +} + +static inline void SetPageHugeFreed(struct page *head) +{ + set_page_private(head + 4, -1UL); +} + +static inline void ClearPageHugeFreed(struct page *head) +{ + set_page_private(head + 4, 0); +} + /* Forward declaration */ static int hugetlb_acct_memory(struct hstate *h, long delta); @@ -1028,6 +1043,7 @@ static void enqueue_huge_page(struct hstate *h, struct page *page) list_move(&page->lru, &h->hugepage_freelists[nid]); h->free_huge_pages++; h->free_huge_pages_node[nid]++; + SetPageHugeFreed(page); } static struct page *dequeue_huge_page_node_exact(struct hstate *h, int nid) @@ -1044,6 +1060,7 @@ static struct page *dequeue_huge_page_node_exact(struct hstate *h, int nid) list_move(&page->lru, &h->hugepage_activelist); set_page_refcounted(page); + ClearPageHugeFreed(page); h->free_huge_pages--; h->free_huge_pages_node[nid]--; return page; @@ -1504,6 +1521,7 @@ static void prep_new_huge_page(struct hstate *h, struct page *page, int nid) spin_lock(&hugetlb_lock); h->nr_huge_pages++; h->nr_huge_pages_node[nid]++; + ClearPageHugeFreed(page); spin_unlock(&hugetlb_lock); } @@ -1770,6 +1788,14 @@ int dissolve_free_huge_page(struct page *page) int nid = page_to_nid(head); if (h->free_huge_pages - h->resv_huge_pages == 0) goto out; + + /* + * We should make sure that the page is already on the free list + * when it is dissolved. + */ + if (unlikely(!PageHugeFreed(head))) + goto out; + /* * Move PageHWPoison flag from head page to the raw error page, * which makes any subpages rather than the error page reusable.
There is a race condition between __free_huge_page() and dissolve_free_huge_page(). CPU0: CPU1: // page_count(page) == 1 put_page(page) __free_huge_page(page) dissolve_free_huge_page(page) spin_lock(&hugetlb_lock) // PageHuge(page) && !page_count(page) update_and_free_page(page) // page is freed to the buddy spin_unlock(&hugetlb_lock) spin_lock(&hugetlb_lock) clear_page_huge_active(page) enqueue_huge_page(page) // It is wrong, the page is already freed spin_unlock(&hugetlb_lock) The race windows is between put_page() and dissolve_free_huge_page(). We should make sure that the page is already on the free list when it is dissolved. Fixes: c8721bbbdd36 ("mm: memory-hotplug: enable memory hotplug to handle hugepage") Signed-off-by: Muchun Song <songmuchun@bytedance.com> Cc: stable@vger.kernel.org --- mm/hugetlb.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+)