Subject: Re: bug#78637: 30.1.90; Calling setopt during init loads cus-start over and over

From: Eli Zaretskii <[email-masked]>
Date: Fri, 30 May 2025 19:11:01 +0300

> From: Aaron Zeng <[email-masked]>
> Date: Fri, 30 May 2025 11:27:44 -0400
> Cc: [email-masked], [email-masked]
> 
> On Fri, May 30, 2025 at 3:24 AM Eli Zaretskii <[email-masked]> wrote:
> >
> > I don't understand: cus-start is preloaded (see lisp/loadup.el), so
> > "(require 'cus-start)" should be (almost) a no-op.  Do you have any
> > evidence that cus-start.el is indeed being loaded under this recipe?
> > If so, can you present that evidence?  (And no, the fact that startup
> > time goes down doesn't mean that loading cus-start is the reason, it
> > could be something else.)
> 
> 
> Yes, if you add (setq force-load-messages t) to the init file I provided,
> you can see that "Loading cus-start..." is messaged repeatedly, once
> for each time setopt runs.  Also, you can see that "Loading cus-start...done"
> never gets messaged, indicating that something failed while loading
> cus-start.  I also observe that just calling (require 'cus-start) in that file
> triggers an error.

This is because:

  . custom.el and cus-edit.el say (require 'cus-start), something they
    don't need to do
  . for some reason, 'require' doesn't avoid loading cus-start even if
    it is already loaded; the simple patch below reduces the number of
    loads of cus-start to just 1 in this scenario

Stefan, why does this happen?  It sounds like when 'require' is called
from bytecode, it always loads the file?  Or is this something special
for cus-start?  Or something else?

Further, that single load of cus-start that is left is also strange:
it comes from cus-load, and stepping with a debugger into Frequire, I
see that cus-start is not in the Vfeatures list?  Why?

> > I think this patch changes behavior, so it is not TRT.  But before we
> > discuss how to fix the problem, let's be sure we understand the
> > problem and its reason(s).
> 
> Sorry, I skipped a few steps in reasoning here.  What I am observing from the
> backtrace of the load error mentioned above, is that
> custom-delayed-init-variables
> is set to t when add-to-list runs (on the line following the context
> from the above patch).  This is, of course, a type error.
> 
> The docstring of custom-delayed-init-variables claims that it is set
> to a non-list
> value when the list has already been processed---so, my reasoning was that
> if it is already set to a non-list value that means there is no point
> adding anything
> to it.
> 
> I am happy to consider different ways of solving this issue.

The way to solve this is to understand why 'require' insists on
loading cus-start although it's preloaded.  I hope Stefan will help us
understand why this happens.

Here's the patch which reduces the number of loads to 1 (and which I
hoped will reduce it to zero):

diff --git a/lisp/cus-edit.el b/lisp/cus-edit.el
index 2ecae54..510c0b9 100644
--- a/lisp/cus-edit.el
+++ b/lisp/cus-edit.el
@@ -149,7 +149,8 @@ recentf-exclude
   (error nil))
 
 (condition-case nil
-    (require 'cus-start)
+    (unless (featurep 'cus-start)
+      (require 'cus-start))
   (error nil))
 
 (put 'custom-define-hook 'custom-type 'hook)
diff --git a/lisp/custom.el b/lisp/custom.el
index a0dc194..e5ceda8 100644
--- a/lisp/custom.el
+++ b/lisp/custom.el
@@ -699,7 +699,8 @@ custom-load-symbol
       (ignore-errors
         (require 'cus-load))
       (ignore-errors
-        (require 'cus-start))
+        (unless (featurep 'cus-start)
+          (require 'cus-start)))
       (dolist (load (get symbol 'custom-loads))
         (cond ((symbolp load) (ignore-errors (require load)))
 	      ;; This is subsumed by the test below, but it's much faster.