Hey Alexander, Thanks a lot for this report. It helps me a lot to understand the existing limitation of OpenBSD's pdaemon.
On 29/11/25(Sat) 00:45, Alexander Bluhm wrote: > Hi, > > My i386 test machine crashed during make build. > > ===> gnu/usr.bin/clang/include/llvm/X86 > ... > /usr/src/gnu/usr.bin/clang/include/llvm/X86/obj/../../../llvm-tblgen/llvm-tblgen > -gen-asm-writer -asmwriternum=1 > -I/usr/src/gnu/usr.bin/clang/include/llvm/X86/../../../../../llvm/llvm/include > > -I/usr/src/gnu/usr.bin/clang/include/llvm/X86/../../../../../llvm/llvm/lib/Target/X86 > -o X86GenAsmWriter1.inc > /usr/src/gnu/usr.bin/clang/include/llvm/X86/../../../../../llvm/llvm/lib/Target/X86/X86.td > Timeout, server ot4 not responding. > 2025-11-28T16:04:30Z Command 'ssh root@ot4 cd /usr/src && time nice make -j > 13 build' failed: 65280 at /data/test/regress/bin/Machine.pm line 232. > > panic: uao_find_swhash_elt: can't allocate entry > Stopped at db_enter+0x4: popl %ebp > TID PID UID PRFLAGS PFLAGS CPU COMMAND > *294827 5534 0 0x14000 0x200 11K pagedaemon > db_enter() at db_enter+0x4 > panic(d0c972a3) at panic+0x7a > uao_set_swslot(d1044a74,26e9a,1a63) at uao_set_swslot+0x184 > uvmpd_scan_inactive(0,15a3) at uvmpd_scan_inactive+0x7b7 > uvmpd_scan(0,15a3,73a) at uvmpd_scan+0x3a > uvm_pageout(d6c4f1b0) at uvm_pageout+0x29b > https://www.openbsd.org/ddb.html describes the minimum info required in bug > reports. Insufficient info makes it difficult to find and fix bugs. Diff below brings some more glue from NetBSD to avoid this problem. It removes the incorrect panic from oga@. The page daemon can hit such limit because releasing pages is asynchronous. Could you please give it a go and report the next panic you find? Thanks a lot! Index: uvm/uvm_pdaemon.c =================================================================== RCS file: /cvs/src/sys/uvm/uvm_pdaemon.c,v diff -u -p -r1.138 uvm_pdaemon.c --- uvm/uvm_pdaemon.c 5 Oct 2025 14:13:22 -0000 1.138 +++ uvm/uvm_pdaemon.c 1 Dec 2025 14:04:07 -0000 @@ -414,6 +414,94 @@ uvmpd_trylockowner(struct vm_page *pg) return slock; } +struct swapcluster { + int swc_slot; + int swc_nallocated; + int swc_nused; + struct vm_page *swc_pages[SWCLUSTPAGES]; +}; + +void +swapcluster_init(struct swapcluster *swc) +{ + swc->swc_slot = 0; + swc->swc_nused = 0; +} + +int +swapcluster_allocslots(struct swapcluster *swc) +{ + int slot, npages; + + if (swc->swc_slot != 0) + return 0; + + npages = SWCLUSTPAGES; + slot = uvm_swap_alloc(&npages, TRUE); + if (slot == 0) + return ENOMEM; + + swc->swc_slot = slot; + swc->swc_nallocated = npages; + swc->swc_nused = 0; + + return 0; +} + +int +swapcluster_add(struct swapcluster *swc, struct vm_page *pg) +{ + int slot; + struct uvm_object *uobj; + + KASSERT(swc->swc_slot != 0); + KASSERT(swc->swc_nused < swc->swc_nallocated); + KASSERT((pg->pg_flags & PQ_SWAPBACKED) != 0); + + slot = swc->swc_slot + swc->swc_nused; + uobj = pg->uobject; + if (uobj == NULL) { + KASSERT(rw_write_held(pg->uanon->an_lock)); + pg->uanon->an_swslot = slot; + } else { + int result; + + KASSERT(rw_write_held(uobj->vmobjlock)); + result = uao_set_swslot(uobj, pg->offset >> PAGE_SHIFT, slot); + if (result == -1) + return ENOMEM; + } + swc->swc_pages[swc->swc_nused] = pg; + swc->swc_nused++; + + return 0; +} + +void +swapcluster_flush(struct swapcluster *swc) +{ + int slot; + int nused; + int nallocated; + + if (swc->swc_slot == 0) + return; + KASSERT(swc->swc_nused <= swc->swc_nallocated); + + slot = swc->swc_slot; + nused = swc->swc_nused; + nallocated = swc->swc_nallocated; + + if (nused < nallocated) + uvm_swap_free(slot + nused, nallocated - nused); +} + +static inline int +swapcluster_nused(struct swapcluster *swc) +{ + return swc->swc_nused; +} + /* * uvmpd_dropswap: free any swap allocated to this page. * @@ -497,10 +585,8 @@ uvmpd_scan_inactive(struct uvm_pmalloc * struct uvm_object *uobj; struct vm_page *pps[SWCLUSTPAGES], **ppsp; int npages; - struct vm_page *swpps[SWCLUSTPAGES]; /* XXX: see below */ + struct swapcluster swc; struct rwlock *slock; - int swnpages, swcpages; /* XXX: see below */ - int swslot; struct vm_anon *anon; boolean_t swap_backed; vaddr_t start; @@ -511,8 +597,7 @@ uvmpd_scan_inactive(struct uvm_pmalloc * * to stay in the loop while we have a page to scan or we have * a swap-cluster to build. */ - swslot = 0; - swnpages = swcpages = 0; + swapcluster_init(&swc); dirtyreacts = 0; p = NULL; @@ -532,7 +617,7 @@ uvmpd_scan_inactive(struct uvm_pmalloc * /* Insert iterator. */ TAILQ_INSERT_AFTER(pglst, p, &iter, pageq); - for (; p != NULL || swslot != 0; p = uvmpd_iterator(pglst, p, &iter)) { + for (; p != NULL || swc.swc_slot != 0; p = uvmpd_iterator(pglst, p, &iter)) { /* * note that p can be NULL iff we have traversed the whole * list and need to do one final swap-backed clustered pageout. @@ -544,9 +629,10 @@ uvmpd_scan_inactive(struct uvm_pmalloc * * see if we've met our target */ if ((uvmpd_pma_done(pma) && - (uvmexp.paging >= (shortage - freed))) || + (uvmexp.paging + swapcluster_nused(&swc) + >= (shortage - freed))) || dirtyreacts == UVMPD_NUMDIRTYREACTS) { - if (swslot == 0) { + if (swc.swc_slot == 0) { /* exit now if no swap-i/o pending */ break; } @@ -701,35 +787,30 @@ uvmpd_scan_inactive(struct uvm_pmalloc * uvmpd_dropswap(p); /* start new cluster (if necessary) */ - if (swslot == 0) { - swnpages = SWCLUSTPAGES; - swslot = uvm_swap_alloc(&swnpages, - TRUE); - if (swslot == 0) { - /* no swap? give up! */ - atomic_clearbits_int( - &p->pg_flags, - PG_BUSY); - UVM_PAGE_OWN(p, NULL); - rw_exit(slock); - continue; - } - swcpages = 0; /* cluster is empty */ + if (swapcluster_allocslots(&swc)) { + atomic_clearbits_int(&p->pg_flags, + PG_BUSY); + UVM_PAGE_OWN(p, NULL); + dirtyreacts++; + uvm_pageactivate(p); + rw_exit(slock); + continue; } /* add block to cluster */ - swpps[swcpages] = p; - if (anon) - anon->an_swslot = swslot + swcpages; - else - uao_set_swslot(uobj, - p->offset >> PAGE_SHIFT, - swslot + swcpages); - swcpages++; + if (swapcluster_add(&swc, p)) { + atomic_clearbits_int(&p->pg_flags, + PG_BUSY); + UVM_PAGE_OWN(p, NULL); + dirtyreacts++; + uvm_pageactivate(p); + rw_exit(slock); + continue; + } rw_exit(slock); /* cluster not full yet? */ - if (swcpages < swnpages) + if (swc.swc_nused < swc.swc_nallocated) continue; } } else { @@ -748,17 +829,14 @@ uvmpd_scan_inactive(struct uvm_pmalloc * */ if (swap_backed) { /* starting I/O now... set up for it */ - npages = swcpages; - ppsp = swpps; + npages = swc.swc_nused; + ppsp = swc.swc_pages; /* for swap-backed pages only */ - start = (vaddr_t) swslot; + start = (vaddr_t) swc.swc_slot; /* if this is final pageout we could have a few * extra swap blocks */ - if (swcpages < swnpages) { - uvm_swap_free(swslot + swcpages, - (swnpages - swcpages)); - } + swapcluster_flush(&swc); } else { /* normal object pageout */ ppsp = pps; @@ -794,9 +872,8 @@ uvmpd_scan_inactive(struct uvm_pmalloc * * if we did i/o to swap, zero swslot to indicate that we are * no longer building a swap-backed cluster. */ - if (swap_backed) - swslot = 0; /* done with this cluster */ + swapcluster_init(&swc); /* done with this cluster */ /* * first, we check for VM_PAGER_PEND which means that the Index: uvm/uvm_aobj.c =================================================================== RCS file: /cvs/src/sys/uvm/uvm_aobj.c,v diff -u -p -r1.119 uvm_aobj.c --- uvm/uvm_aobj.c 10 Nov 2025 10:53:53 -0000 1.119 +++ uvm/uvm_aobj.c 1 Dec 2025 11:15:02 -0000 @@ -142,7 +142,7 @@ struct uvm_aobj { struct pool uvm_aobj_pool; static struct uao_swhash_elt *uao_find_swhash_elt(struct uvm_aobj *, int, - boolean_t); + boolean_t, boolean_t); static boolean_t uao_flush(struct uvm_object *, voff_t, voff_t, int); static void uao_free(struct uvm_aobj *); @@ -197,10 +197,12 @@ static struct mutex uao_list_lock = MUTE * offset. */ static struct uao_swhash_elt * -uao_find_swhash_elt(struct uvm_aobj *aobj, int pageidx, boolean_t create) +uao_find_swhash_elt(struct uvm_aobj *aobj, int pageidx, boolean_t create, + boolean_t wait) { struct uao_swhash *swhash; struct uao_swhash_elt *elt; + int waitf = wait ? PR_WAITOK : PR_NOWAIT; voff_t page_tag; swhash = UAO_SWHASH_HASH(aobj, pageidx); /* first hash to get bucket */ @@ -220,17 +222,9 @@ uao_find_swhash_elt(struct uvm_aobj *aob /* * allocate a new entry for the bucket and init/insert it in */ - elt = pool_get(&uao_swhash_elt_pool, PR_NOWAIT | PR_ZERO); - /* - * XXX We cannot sleep here as the hash table might disappear - * from under our feet. And we run the risk of deadlocking - * the pagedeamon. In fact this code will only be called by - * the pagedaemon and allocation will only fail if we - * exhausted the pagedeamon reserve. In that case we're - * doomed anyway, so panic. - */ + elt = pool_get(&uao_swhash_elt_pool, waitf | PR_ZERO); if (elt == NULL) - panic("%s: can't allocate entry", __func__); + return NULL; LIST_INSERT_HEAD(swhash, elt, list); elt->tag = page_tag; @@ -258,7 +252,7 @@ uao_find_swslot(struct uvm_object *uobj, */ if (UAO_USES_SWHASH(aobj)) { struct uao_swhash_elt *elt = - uao_find_swhash_elt(aobj, pageidx, FALSE); + uao_find_swhash_elt(aobj, pageidx, FALSE, FALSE); if (elt) return UAO_SWHASH_ELT_PAGESLOT(elt, pageidx); @@ -284,6 +278,7 @@ int uao_set_swslot(struct uvm_object *uobj, int pageidx, int slot) { struct uvm_aobj *aobj = (struct uvm_aobj *)uobj; + struct uao_swhash_elt *elt; int oldslot; KASSERT(rw_write_held(uobj->vmobjlock) || uobj->uo_refs == 0); @@ -310,11 +305,9 @@ uao_set_swslot(struct uvm_object *uobj, * the page had not swap slot in the first place, and * we are freeing. */ - struct uao_swhash_elt *elt = - uao_find_swhash_elt(aobj, pageidx, slot ? TRUE : FALSE); + elt = uao_find_swhash_elt(aobj, pageidx, slot != 0, FALSE); if (elt == NULL) { - KASSERT(slot == 0); - return 0; + return slot ? - 1 : 0; } oldslot = UAO_SWHASH_ELT_PAGESLOT(elt, pageidx); @@ -465,7 +458,7 @@ uao_shrink_convert(struct uvm_object *uo /* Convert swap slots from hash to array. */ for (i = 0; i < pages; i++) { - elt = uao_find_swhash_elt(aobj, i, FALSE); + elt = uao_find_swhash_elt(aobj, i, FALSE, FALSE); if (elt != NULL) { new_swslots[i] = UAO_SWHASH_ELT_PAGESLOT(elt, i); if (new_swslots[i] != 0) @@ -622,12 +615,12 @@ uao_grow_convert(struct uvm_object *uobj /* Set these now, so we can use uao_find_swhash_elt(). */ old_swslots = aobj->u_swslots; - aobj->u_swhash = new_swhash; + aobj->u_swhash = new_swhash; aobj->u_swhashmask = new_hashmask; for (i = 0; i < aobj->u_pages; i++) { if (old_swslots[i] != 0) { - elt = uao_find_swhash_elt(aobj, i, TRUE); + elt = uao_find_swhash_elt(aobj, i, TRUE, TRUE); elt->count++; UAO_SWHASH_ELT_PAGESLOT(elt, i) = old_swslots[i]; }
