Author: brane
Date: Mon Jan 12 17:00:45 2026
New Revision: 1931269

Log:
On the serf_error_callbacks branch: Add Serf error callback handling
using a context callback with our session as the baton. Assumes that
we use one Serf context per ra_serf session, which is currently true.

[in subversion/libsvn_ra_serf]
* ra_serf.h
  (svn_ra_serf__session_t::serf_error): New member.
  (svn_ra_serf__wrap_err_stack): New prototype and debug-mode macro.
* serf.c
  (sess_error_callback): New; the Serf error callback.
  (svn_ra_serf__open, ra_serf_dup_session): Register the callback when
   the Serf context is created.
* util.c: Optionally include serf_bucket_util.h; it declares the required
   prototype for serf_default_readline().
  (conn_setup): When a new Serf SSL context was created, tell it to use
   our session's error callback.
  (svn_ra_serf__context_run): Report errors collected from Serf.
  (bucket_limited_readline): Put this function in a conditional block,
   otherwise we get a warning about an unused function when experimental
   Serf features are turned on.
* util_error.c: Include stdarg.h.
  (format_error_message): New; hoist error mesage formatting from
   svn_ra_serf__wrap_err().
  (svn_ra_serf__wrap_err): Delegate to format_error_message().
  (svn_ra_serf__wrap_err_stack): Implement, using format_error_message().

Modified:
   subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/ra_serf.h
   subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/serf.c
   subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/util.c
   
subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/util_error.c

Modified: 
subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/ra_serf.h
==============================================================================
--- 
subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/ra_serf.h    
    Mon Jan 12 16:32:44 2026        (r1931268)
+++ 
subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/ra_serf.h    
    Mon Jan 12 17:00:45 2026        (r1931269)
@@ -172,6 +172,10 @@ struct svn_ra_serf__session_t {
   /* Error that we've received but not yet returned upstream. */
   svn_error_t *pending_error;
 
+  /* Errors gathered from Serf error callbacks.
+     FIXME: Can we just use pending_error? */
+  svn_error_t *serf_error;
+
   /* List of authn types supported by the client.*/
   int authn_types;
 
@@ -1570,14 +1574,23 @@ svn_ra_serf__create_sb_bucket(svn_spillb
                               apr_pool_t *result_pool,
                               apr_pool_t *scratch_pool);
 
-/** Wrap STATUS from an serf function. If STATUS is not serf error code,
-  * this is equivalent to svn_error_wrap_apr().
+/* Wrap STATUS from an serf function. If STATUS is not serf error code,
+ * this is equivalent to svn_error_wrap_apr().
  */
 svn_error_t *
 svn_ra_serf__wrap_err(apr_status_t status,
                       const char *fmt,
                       ...);
 
+/* Like svn_ra_serf__wrap_err but also accept a child error to maintain
+ * an error stack.
+ */
+svn_error_t *
+svn_ra_serf__wrap_err_stack(apr_status_t status,
+                            svn_error_t *child,
+                            const char *fmt,
+                            ...);
+
 /* Create a bucket that just returns DATA (with length LEN) and then returns
    the APR_EAGAIN status */
 serf_bucket_t *
@@ -1675,7 +1688,8 @@ svn_ra_serf__default_readline(serf_bucke
 /* Wrapper macros to collect file and line information */
 #define svn_ra_serf__wrap_err \
   (svn_error__locate(__FILE__,__LINE__), (svn_ra_serf__wrap_err))
-
+#define svn_ra_serf__wrap_err_stack \
+  (svn_error__locate(__FILE__,__LINE__), (svn_ra_serf__wrap_err_stack))
 #endif
 
 #ifdef __cplusplus

Modified: 
subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/serf.c
==============================================================================
--- subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/serf.c   
Mon Jan 12 16:32:44 2026        (r1931268)
+++ subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/serf.c   
Mon Jan 12 17:00:45 2026        (r1931269)
@@ -481,6 +481,22 @@ get_user_agent_string(apr_pool_t *pool)
                       major, minor, patch);
 }
 
+#if defined(SVN__SERF_EXPERIMENTAL) && SERF_VERSION_AT_LEAST(1, 5, 0)
+static apr_status_t
+sess_error_callback(void *baton,
+                    unsigned source,
+                    apr_status_t status,
+                    const char *message)
+{
+  const char* prefix = (source & SERF_ERROR_CB_SSL_CONTEXT) ? "TLS" : "NET";
+  svn_ra_serf__session_t *const session = baton;
+
+  session->serf_error = svn_ra_serf__wrap_err_stack(
+      status, session->serf_error, _("%s: %s"), prefix, message);
+  return SVN_NO_ERROR;
+}
+#endif
+
 /* Implements svn_ra__vtable_t.open_session(). */
 static svn_error_t *
 svn_ra_serf__open(svn_ra_session_t *session,
@@ -520,7 +536,17 @@ svn_ra_serf__open(svn_ra_session_t *sess
   serf_sess->cancel_baton = callback_baton;
 
   /* todo: reuse serf context across sessions */
+
+  /* odot: For now, it's better to keep context-per-session, otherwise
+           dealing with Serf's error callbacks will be a real pain.
+           It would be different if we could guarantee a single Serf
+           context per svn_client_ctx_t. */
   serf_sess->context = serf_context_create(serf_sess->pool);
+#if defined(SVN__SERF_EXPERIMENTAL) && SERF_VERSION_AT_LEAST(1, 5, 0)
+  serf_context_error_callback_set(serf_sess->context,
+                                  sess_error_callback,
+                                  serf_sess);
+#endif
 
   SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache,
                                        serf_sess->pool));
@@ -775,6 +801,11 @@ ra_serf_dup_session(svn_ra_session_t *ne
   /* conn_latency */
 
   new_sess->context = serf_context_create(result_pool);
+#if defined(SVN__SERF_EXPERIMENTAL) && SERF_VERSION_AT_LEAST(1, 5, 0)
+  serf_context_error_callback_set(new_sess->context,
+                                  sess_error_callback,
+                                  new_sess);
+#endif
 
   SVN_ERR(load_config(new_sess, old_sess->config,
                       result_pool, scratch_pool));

Modified: 
subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/util.c
==============================================================================
--- subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/util.c   
Mon Jan 12 16:32:44 2026        (r1931268)
+++ subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/util.c   
Mon Jan 12 17:00:45 2026        (r1931269)
@@ -31,6 +31,9 @@
 
 #include <serf.h>
 #include <serf_bucket_types.h>
+#if defined(SVN__SERF_EXPERIMENTAL) && SERF_VERSION_AT_LEAST(1, 5, 0)
+#include <serf_bucket_util.h>
+#endif
 
 #include "svn_hash.h"
 #include "svn_dirent_uri.h"
@@ -549,6 +552,11 @@ conn_setup(apr_socket_t *sock,
                                             ssl_server_cert_cb,
                                             conn);
 
+#if defined(SVN__SERF_EXPERIMENTAL) && SERF_VERSION_AT_LEAST(1, 5, 0)
+          serf_ssl_use_context_error_callback(conn->ssl_context,
+                                              conn->session->context);
+#endif
+
           /* See if the user wants us to trust "default" openssl CAs. */
           if (conn->session->trust_default_ca)
             {
@@ -954,16 +962,20 @@ svn_ra_serf__context_run(svn_ra_serf__se
   SVN_ERR(err);
   if (status)
     {
+      svn_error_t *const collected_errors = sess->serf_error;
+      sess->serf_error = NULL;
+
       /* ### This omits SVN_WARNING, and possibly relies on the fact that
          ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */
       if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
         {
           /* apr can't translate subversion errors to text */
-          SVN_ERR_W(svn_error_create(status, NULL, NULL),
+          SVN_ERR_W(svn_error_create(status, collected_errors, NULL),
                     _("Error running context"));
         }
 
-      return svn_ra_serf__wrap_err(status, _("Error running context"));
+      return svn_ra_serf__wrap_err_stack(status, collected_errors,
+                                         _("Error running context"));
     }
 
   return SVN_NO_ERROR;
@@ -2159,6 +2171,7 @@ svn_ra_serf__get_dirent_props(apr_uint32
   return props;
 }
 
+#if !defined(SVN__SERF_EXPERIMENTAL) || !SERF_VERSION_AT_LEAST(1, 5, 0)
 static apr_status_t
 bucket_limited_readline(serf_bucket_t *bucket, int acceptable,
                         apr_size_t requested, int *found,
@@ -2271,6 +2284,7 @@ bucket_limited_readline(serf_bucket_t *b
 
   return status;
 }
+#endif
 
 apr_status_t
 svn_ra_serf__default_readline(serf_bucket_t *bucket, int acceptable,

Modified: 
subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/util_error.c
==============================================================================
--- 
subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/util_error.c 
    Mon Jan 12 16:32:44 2026        (r1931268)
+++ 
subversion/branches/serf_error_callbacks/subversion/libsvn_ra_serf/util_error.c 
    Mon Jan 12 17:00:45 2026        (r1931269)
@@ -20,6 +20,9 @@
  *    under the License.
  * ====================================================================
  */
+
+#include <stdarg.h>
+
 #include <serf.h>
 
 #include "svn_utf.h"
@@ -41,18 +44,16 @@
 #undef svn_error_quick_wrap
 #undef svn_error_wrap_apr
 #undef svn_ra_serf__wrap_err
+#undef svn_ra_serf__wrap_err_stack
 
-svn_error_t *
-svn_ra_serf__wrap_err(apr_status_t status,
-                      const char *fmt,
-                      ...)
-{
-  const char *serf_err_msg = serf_error_string(status);
-  svn_error_t *err;
-  va_list ap;
-
-  err = svn_error_create(status, NULL, NULL);
 
+static void
+format_error_message(svn_error_t *err,
+                     apr_status_t status,
+                     const char *serf_err_msg,
+                     const char *fmt,
+                     va_list args)
+{
   if (serf_err_msg || fmt)
     {
       const char *msg;
@@ -78,9 +79,7 @@ svn_ra_serf__wrap_err(apr_status_t statu
       /* Append it to the formatted message. */
       if (fmt)
         {
-          va_start(ap, fmt);
-          msg = apr_pvsprintf(err->pool, fmt, ap);
-          va_end(ap);
+          msg = apr_pvsprintf(err->pool, fmt, args);
         }
       else
         {
@@ -96,6 +95,37 @@ svn_ra_serf__wrap_err(apr_status_t statu
           err->message = msg;
         }
     }
+}
+
+
+svn_error_t *
+svn_ra_serf__wrap_err(apr_status_t status,
+                      const char *fmt,
+                      ...)
+{
+  svn_error_t *err;
+  va_list args;
+
+  err = svn_error_create(status, NULL, NULL);
+  va_start(args, fmt);
+  format_error_message(err, status, serf_error_string(status), fmt, args);
+  va_end(args);
+  return err;
+}
+
+
+svn_error_t *
+svn_ra_serf__wrap_err_stack(apr_status_t status,
+                            svn_error_t *child,
+                            const char *fmt,
+                            ...)
+{
+  svn_error_t *err;
+  va_list args;
 
+  err = svn_error_create(status, child, NULL);
+  va_start(args, fmt);
+  format_error_message(err, status, serf_error_string(status), fmt, args);
+  va_end(args);
   return err;
 }

Reply via email to