2 * replay.c : entry point for replay RA functions for ra_serf
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
29 #include "svn_pools.h"
34 #include "../libsvn_ra/ra_loader.h"
35 #include "svn_config.h"
36 #include "svn_delta.h"
37 #include "svn_base64.h"
39 #include "svn_private_config.h"
41 #include "private/svn_string_private.h"
47 * This enum represents the current state of our XML parsing.
49 typedef enum replay_state_e {
50 INITIAL = XML_STATE_INITIAL,
53 REPLAY_TARGET_REVISION,
55 REPLAY_OPEN_DIRECTORY,
61 REPLAY_CLOSE_DIRECTORY,
62 REPLAY_CHANGE_DIRECTORY_PROP,
63 REPLAY_CHANGE_FILE_PROP,
64 REPLAY_APPLY_TEXTDELTA
67 #define S_ SVN_XML_NAMESPACE
68 static const svn_ra_serf__xml_transition_t replay_ttable[] = {
69 { INITIAL, S_, "editor-report", REPLAY_REPORT,
70 FALSE, { NULL }, TRUE },
72 /* Replay just throws every operation as xml element directly
73 in the replay report, so we can't really use the nice exit
74 handling of the transition parser to handle clean callbacks */
76 { REPLAY_REPORT, S_, "target-revision", REPLAY_TARGET_REVISION,
77 FALSE, { "rev", NULL }, TRUE },
79 { REPLAY_REPORT, S_, "open-root", REPLAY_OPEN_ROOT,
80 FALSE, { "rev", NULL }, TRUE },
82 { REPLAY_REPORT, S_, "open-directory", REPLAY_OPEN_DIRECTORY,
83 FALSE, { "name", "rev", NULL }, TRUE },
85 { REPLAY_REPORT, S_, "open-file", REPLAY_OPEN_FILE,
86 FALSE, { "name", "rev", NULL }, TRUE },
88 { REPLAY_REPORT, S_, "add-directory", REPLAY_ADD_DIRECTORY,
89 FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", NULL}, TRUE },
91 { REPLAY_REPORT, S_, "add-file", REPLAY_ADD_FILE,
92 FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", NULL}, TRUE },
94 { REPLAY_REPORT, S_, "delete-entry", REPLAY_DELETE_ENTRY,
95 FALSE, { "name", "rev", NULL }, TRUE },
97 { REPLAY_REPORT, S_, "close-file", REPLAY_CLOSE_FILE,
98 FALSE, { "?checksum", NULL }, TRUE },
100 { REPLAY_REPORT, S_, "close-directory", REPLAY_CLOSE_DIRECTORY,
101 FALSE, { NULL }, TRUE },
103 { REPLAY_REPORT, S_, "change-dir-prop", REPLAY_CHANGE_DIRECTORY_PROP,
104 TRUE, { "name", "?del", NULL }, TRUE },
106 { REPLAY_REPORT, S_, "change-file-prop", REPLAY_CHANGE_FILE_PROP,
107 TRUE, { "name", "?del", NULL }, TRUE },
109 { REPLAY_REPORT, S_, "apply-textdelta", REPLAY_APPLY_TEXTDELTA,
110 FALSE, { "?checksum", NULL }, TRUE },
115 /* Per directory/file state */
116 typedef struct replay_node_t {
117 apr_pool_t *pool; /* pool allocating this node's data */
118 svn_boolean_t file; /* file or dir */
120 void *baton; /* node baton */
121 svn_stream_t *stream; /* stream while handling txdata */
123 struct replay_node_t *parent; /* parent node or NULL */
126 /* Per revision replay report state */
127 typedef struct revision_report_t {
128 apr_pool_t *pool; /* per revision pool */
130 struct replay_node_t *current_node;
131 struct replay_node_t *root_node;
133 /* Are we done fetching this file?
134 Handles book-keeping in multi-report case */
136 int *replay_reports; /* NULL or number of outstanding reports */
138 /* callback to get an editor */
139 svn_ra_replay_revstart_callback_t revstart_func;
140 svn_ra_replay_revfinish_callback_t revfinish_func;
143 /* replay receiver function and baton */
144 const svn_delta_editor_t *editor;
147 /* Path and revision used to filter replayed changes. If
148 INCLUDE_PATH is non-NULL, REVISION is unnecessary and will not be
149 included in the replay REPORT. (Because the REPORT is being
150 aimed an HTTP v2 revision resource.) */
151 const char *include_path;
152 svn_revnum_t revision;
154 /* Information needed to create the replay report body */
155 svn_revnum_t low_water_mark;
156 svn_boolean_t send_deltas;
158 /* Target and revision to fetch revision properties on */
159 const char *revprop_target;
160 svn_revnum_t revprop_rev;
162 /* Revision properties for this revision. */
163 apr_hash_t *rev_props;
165 /* Handlers for the PROPFIND and REPORT for the current revision. */
166 svn_ra_serf__handler_t *propfind_handler;
167 svn_ra_serf__handler_t *report_handler; /* For done handler */
169 svn_ra_serf__session_t *session;
173 /* Conforms to svn_ra_serf__xml_opened_t */
175 replay_opened(svn_ra_serf__xml_estate_t *xes,
178 const svn_ra_serf__dav_props_t *tag,
179 apr_pool_t *scratch_pool)
181 struct revision_report_t *ctx = baton;
183 if (entered_state == REPLAY_REPORT)
185 /* Before we can continue, we need the revision properties. */
186 SVN_ERR_ASSERT(!ctx->propfind_handler || ctx->propfind_handler->done);
188 svn_ra_serf__keep_only_regular_props(ctx->rev_props, scratch_pool);
190 if (ctx->revstart_func)
192 SVN_ERR(ctx->revstart_func(ctx->revision, ctx->replay_baton,
193 &ctx->editor, &ctx->editor_baton,
198 else if (entered_state == REPLAY_APPLY_TEXTDELTA)
200 struct replay_node_t *node = ctx->current_node;
202 const char *checksum;
203 svn_txdelta_window_handler_t handler;
206 if (! node || ! node->file || node->stream)
207 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
209 /* ### Is there a better way to access a specific attr here? */
210 attrs = svn_ra_serf__xml_gather_since(xes, REPLAY_APPLY_TEXTDELTA);
211 checksum = svn_hash_gets(attrs, "checksum");
213 SVN_ERR(ctx->editor->apply_textdelta(node->baton, checksum, node->pool,
214 &handler, &handler_baton));
216 if (handler != svn_delta_noop_window_handler)
218 node->stream = svn_base64_decode(
219 svn_txdelta_parse_svndiff(handler,
230 /* Conforms to svn_ra_serf__xml_closed_t */
232 replay_closed(svn_ra_serf__xml_estate_t *xes,
235 const svn_string_t *cdata,
237 apr_pool_t *scratch_pool)
239 struct revision_report_t *ctx = baton;
241 if (leaving_state == REPLAY_REPORT)
243 if (ctx->current_node)
244 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
246 if (ctx->revfinish_func)
248 SVN_ERR(ctx->revfinish_func(ctx->revision, ctx->replay_baton,
249 ctx->editor, ctx->editor_baton,
250 ctx->rev_props, scratch_pool));
253 else if (leaving_state == REPLAY_TARGET_REVISION)
255 const char *revstr = svn_hash_gets(attrs, "rev");
258 SVN_ERR(svn_cstring_atoi64(&rev, revstr));
259 SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
263 else if (leaving_state == REPLAY_OPEN_ROOT)
265 const char *revstr = svn_hash_gets(attrs, "rev");
267 apr_pool_t *root_pool = svn_pool_create(ctx->pool);
269 if (ctx->current_node || ctx->root_node)
270 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
272 ctx->root_node = apr_pcalloc(root_pool, sizeof(*ctx->root_node));
273 ctx->root_node->pool = root_pool;
275 ctx->current_node = ctx->root_node;
277 SVN_ERR(svn_cstring_atoi64(&rev, revstr));
278 SVN_ERR(ctx->editor->open_root(ctx->editor_baton, (svn_revnum_t)rev,
280 &ctx->current_node->baton));
282 else if (leaving_state == REPLAY_OPEN_DIRECTORY
283 || leaving_state == REPLAY_OPEN_FILE
284 || leaving_state == REPLAY_ADD_DIRECTORY
285 || leaving_state == REPLAY_ADD_FILE)
287 struct replay_node_t *node;
288 apr_pool_t *node_pool;
289 const char *name = svn_hash_gets(attrs, "name");
293 if (!ctx->current_node || ctx->current_node->file)
294 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
296 node_pool = svn_pool_create(ctx->current_node->pool);
297 node = apr_pcalloc(node_pool, sizeof(*node));
298 node->pool = node_pool;
299 node->parent = ctx->current_node;
301 if (leaving_state == REPLAY_OPEN_DIRECTORY
302 || leaving_state == REPLAY_OPEN_FILE)
304 rev_str = svn_hash_gets(attrs, "rev");
307 rev_str = svn_hash_gets(attrs, "copyfrom-rev");
310 SVN_ERR(svn_cstring_atoi64(&rev, rev_str));
312 rev = SVN_INVALID_REVNUM;
314 switch (leaving_state)
316 case REPLAY_OPEN_DIRECTORY:
318 SVN_ERR(ctx->editor->open_directory(name,
319 ctx->current_node->baton,
324 case REPLAY_OPEN_FILE:
326 SVN_ERR(ctx->editor->open_file(name,
327 ctx->current_node->baton,
332 case REPLAY_ADD_DIRECTORY:
334 SVN_ERR(ctx->editor->add_directory(
336 ctx->current_node->baton,
337 SVN_IS_VALID_REVNUM(rev)
338 ? svn_hash_gets(attrs, "copyfrom-path")
344 case REPLAY_ADD_FILE:
346 SVN_ERR(ctx->editor->add_file(
348 ctx->current_node->baton,
349 SVN_IS_VALID_REVNUM(rev)
350 ? svn_hash_gets(attrs, "copyfrom-path")
356 /* default: unreachable */
358 ctx->current_node = node;
360 else if (leaving_state == REPLAY_CLOSE_FILE)
362 struct replay_node_t *node = ctx->current_node;
364 if (! node || ! node->file)
365 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
367 SVN_ERR(ctx->editor->close_file(node->baton,
368 svn_hash_gets(attrs, "checksum"),
370 ctx->current_node = node->parent;
371 svn_pool_destroy(node->pool);
373 else if (leaving_state == REPLAY_CLOSE_DIRECTORY)
375 struct replay_node_t *node = ctx->current_node;
377 if (! node || node->file)
378 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
380 SVN_ERR(ctx->editor->close_directory(node->baton, node->pool));
381 ctx->current_node = node->parent;
382 svn_pool_destroy(node->pool);
384 else if (leaving_state == REPLAY_DELETE_ENTRY)
386 struct replay_node_t *parent_node = ctx->current_node;
387 const char *name = svn_hash_gets(attrs, "name");
388 const char *revstr = svn_hash_gets(attrs, "rev");
391 if (! parent_node || parent_node->file)
392 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
394 SVN_ERR(svn_cstring_atoi64(&rev, revstr));
395 SVN_ERR(ctx->editor->delete_entry(name,
400 else if (leaving_state == REPLAY_CHANGE_FILE_PROP
401 || leaving_state == REPLAY_CHANGE_DIRECTORY_PROP)
403 struct replay_node_t *node = ctx->current_node;
405 const svn_string_t *value;
407 if (! node || node->file != (leaving_state == REPLAY_CHANGE_FILE_PROP))
408 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
410 name = svn_hash_gets(attrs, "name");
412 if (svn_hash_gets(attrs, "del"))
415 value = svn_base64_decode_string(cdata, scratch_pool);
419 SVN_ERR(ctx->editor->change_file_prop(node->baton, name, value,
424 SVN_ERR(ctx->editor->change_dir_prop(node->baton, name, value,
428 else if (leaving_state == REPLAY_APPLY_TEXTDELTA)
430 struct replay_node_t *node = ctx->current_node;
432 if (! node || ! node->file)
433 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
436 SVN_ERR(svn_stream_close(node->stream));
443 /* Conforms to svn_ra_serf__xml_cdata_t */
445 replay_cdata(svn_ra_serf__xml_estate_t *xes,
450 apr_pool_t *scratch_pool)
452 struct revision_report_t *ctx = baton;
454 if (current_state == REPLAY_APPLY_TEXTDELTA)
456 struct replay_node_t *node = ctx->current_node;
458 if (! node || ! node->file)
459 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
463 apr_size_t written = len;
465 SVN_ERR(svn_stream_write(node->stream, data, &written));
467 return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
468 _("Error writing stream: unexpected EOF"));
475 /* Implements svn_ra_serf__request_body_delegate_t */
477 create_replay_body(serf_bucket_t **bkt,
479 serf_bucket_alloc_t *alloc,
480 apr_pool_t *pool /* request pool */,
481 apr_pool_t *scratch_pool)
483 struct revision_report_t *ctx = baton;
484 serf_bucket_t *body_bkt;
486 body_bkt = serf_bucket_aggregate_create(alloc);
488 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
490 "xmlns:S", SVN_XML_NAMESPACE,
493 /* If we have a non-NULL include path, we add it to the body and
494 omit the revision; otherwise, the reverse. */
495 if (ctx->include_path)
497 svn_ra_serf__add_tag_buckets(body_bkt,
504 svn_ra_serf__add_tag_buckets(body_bkt,
506 apr_ltoa(pool, ctx->revision),
509 svn_ra_serf__add_tag_buckets(body_bkt,
511 apr_ltoa(pool, ctx->low_water_mark),
514 svn_ra_serf__add_tag_buckets(body_bkt,
516 apr_ltoa(pool, ctx->send_deltas),
519 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "S:replay-report");
526 svn_ra_serf__replay(svn_ra_session_t *ra_session,
527 svn_revnum_t revision,
528 svn_revnum_t low_water_mark,
529 svn_boolean_t send_deltas,
530 const svn_delta_editor_t *editor,
532 apr_pool_t *scratch_pool)
534 struct revision_report_t ctx = { NULL };
535 svn_ra_serf__session_t *session = ra_session->priv;
536 svn_ra_serf__handler_t *handler;
537 svn_ra_serf__xml_context_t *xmlctx;
538 const char *report_target;
540 SVN_ERR(svn_ra_serf__report_resource(&report_target, session,
543 ctx.pool = svn_pool_create(scratch_pool);
545 ctx.editor_baton = edit_baton;
547 ctx.revision = revision;
548 ctx.low_water_mark = low_water_mark;
549 ctx.send_deltas = send_deltas;
550 ctx.rev_props = apr_hash_make(scratch_pool);
552 xmlctx = svn_ra_serf__xml_context_create(replay_ttable,
553 replay_opened, replay_closed,
558 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL,
561 handler->method = "REPORT";
562 handler->path = session->session_url.path;
563 handler->body_delegate = create_replay_body;
564 handler->body_delegate_baton = &ctx;
565 handler->body_type = "text/xml";
567 /* Not setting up done handler as we don't use a global context */
569 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
571 if (handler->sline.code != 200)
572 SVN_ERR(svn_ra_serf__unexpected_status(handler));
577 /* The maximum number of outstanding requests at any time. When this
578 * number is reached, ra_serf will stop sending requests until
579 * responses on the previous requests are received and handled.
581 * Some observations about serf which lead us to the current value.
582 * ----------------------------------------------------------------
584 * We aim to keep serf's outgoing queue filled with enough requests so
585 * the network bandwidth and server capacity is used
586 * optimally. Originally we used 5 as the max. number of outstanding
587 * requests, but this turned out to be too low.
589 * Serf doesn't exit out of the svn_ra_serf__context_run_wait loop as long as
590 * it has data to send or receive. With small responses (revs of a few
591 * kB), serf doesn't come out of this loop at all. So with
592 * MAX_OUTSTANDING_REQUESTS set to a low number, there's a big chance
593 * that serf handles those requests completely in its internal loop,
594 * and only then gives us a chance to create new requests. This
595 * results in hiccups, slowing down the whole process.
597 * With a larger MAX_OUTSTANDING_REQUESTS, like 100 or more, there's
598 * more chance that serf can come out of its internal loop so we can
599 * replenish the outgoing request queue. There's no real disadvantage
600 * of using a large number here, besides the memory used to store the
601 * message, parser and handler objects (approx. 250 bytes).
603 * In my test setup peak performance was reached at max. 30-35
604 * requests. So I added a small margin and chose 50.
606 #define MAX_OUTSTANDING_REQUESTS 50
608 /* Implements svn_ra_serf__response_done_delegate_t for svn_ra_serf__replay_range */
610 replay_done(serf_request_t *request,
612 apr_pool_t *scratch_pool)
614 struct revision_report_t *ctx = baton;
615 svn_ra_serf__handler_t *handler = ctx->report_handler;
617 if (handler->server_error)
618 return svn_ra_serf__server_error_create(handler, scratch_pool);
619 else if (handler->sline.code != 200)
620 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
622 *ctx->done = TRUE; /* Breaks out svn_ra_serf__context_run_wait */
624 /* Are re replaying multiple revisions? */
625 if (ctx->replay_reports)
627 (*ctx->replay_reports)--;
630 svn_pool_destroy(ctx->pool); /* Destroys handler and request! */
635 /* Implements svn_ra_serf__request_header_delegate_t */
637 setup_headers(serf_bucket_t *headers,
639 apr_pool_t *request_pool,
640 apr_pool_t *scratch_pool)
642 struct revision_report_t *ctx = baton;
644 svn_ra_serf__setup_svndiff_accept_encoding(headers, ctx->session);
650 svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
651 svn_revnum_t start_revision,
652 svn_revnum_t end_revision,
653 svn_revnum_t low_water_mark,
654 svn_boolean_t send_deltas,
655 svn_ra_replay_revstart_callback_t revstart_func,
656 svn_ra_replay_revfinish_callback_t revfinish_func,
658 apr_pool_t *scratch_pool)
660 svn_ra_serf__session_t *session = ra_session->priv;
661 svn_revnum_t rev = start_revision;
662 const char *report_target;
663 int active_reports = 0;
664 const char *include_path;
666 apr_pool_t *subpool = svn_pool_create(scratch_pool);
668 if (session->http20) {
669 /* ### Auch... this doesn't work yet...
671 This code relies on responses coming in in an exact order, while
672 http2 does everything to deliver responses as fast as possible.
674 With http/1.1 we were quite lucky that this worked, as serf doesn't
675 promise in order delivery.... (Please do not use authz with keys
678 For now fall back to the legacy callback in libsvn_ra that is
679 used by all the other ra layers as workaround.
683 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
686 SVN_ERR(svn_ra_serf__report_resource(&report_target, session,
689 /* Prior to 1.8, mod_dav_svn expect to get replay REPORT requests
690 aimed at the session URL. But that's incorrect -- these reports
691 aren't about specific resources -- they are above revisions. The
692 path-based filtering offered by this API is just that: a filter
693 applied to the full set of changes made in the revision. As
694 such, the correct target for these REPORT requests is the "me
695 resource" (or, pre-http-v2, the default VCC).
697 Our server should have told us if it supported this protocol
698 correction. If so, we aimed our report at the correct resource
699 and include the filtering path as metadata within the report
700 body. Otherwise, we fall back to the pre-1.8 behavior and just
704 http://subversion.tigris.org/issues/show_bug.cgi?id=4287
706 if (session->supports_rev_rsrc_replay)
708 SVN_ERR(svn_ra_serf__get_relative_path(&include_path,
709 session->session_url.path,
717 while (active_reports || rev <= end_revision)
719 if (session->cancel_func)
720 SVN_ERR(session->cancel_func(session->cancel_baton));
722 /* Send pending requests, if any. Limit the number of outstanding
723 requests to MAX_OUTSTANDING_REQUESTS. */
724 if (rev <= end_revision && active_reports < MAX_OUTSTANDING_REQUESTS)
726 struct revision_report_t *rev_ctx;
727 svn_ra_serf__handler_t *handler;
728 apr_pool_t *rev_pool = svn_pool_create(subpool);
729 svn_ra_serf__xml_context_t *xmlctx;
730 const char *replay_target;
732 rev_ctx = apr_pcalloc(rev_pool, sizeof(*rev_ctx));
733 rev_ctx->pool = rev_pool;
734 rev_ctx->revstart_func = revstart_func;
735 rev_ctx->revfinish_func = revfinish_func;
736 rev_ctx->replay_baton = replay_baton;
737 rev_ctx->done = &done;
738 rev_ctx->replay_reports = &active_reports;
739 rev_ctx->include_path = include_path;
740 rev_ctx->revision = rev;
741 rev_ctx->low_water_mark = low_water_mark;
742 rev_ctx->send_deltas = send_deltas;
743 rev_ctx->session = session;
745 /* Request all properties of a certain revision. */
746 rev_ctx->rev_props = apr_hash_make(rev_ctx->pool);
748 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
750 rev_ctx->revprop_target = apr_psprintf(rev_pool, "%s/%ld",
751 session->rev_stub, rev);
752 rev_ctx->revprop_rev = SVN_INVALID_REVNUM;
756 rev_ctx->revprop_target = report_target;
757 rev_ctx->revprop_rev = rev;
760 SVN_ERR(svn_ra_serf__create_propfind_handler(
761 &rev_ctx->propfind_handler,
763 rev_ctx->revprop_target,
764 rev_ctx->revprop_rev,
766 svn_ra_serf__deliver_svn_props,
770 /* Spin up the serf request for the PROPFIND. */
771 svn_ra_serf__request_create(rev_ctx->propfind_handler);
773 /* Send the replay REPORT request. */
774 if (session->supports_rev_rsrc_replay)
776 replay_target = apr_psprintf(rev_pool, "%s/%ld",
777 session->rev_stub, rev);
781 replay_target = session->session_url.path;
784 xmlctx = svn_ra_serf__xml_context_create(replay_ttable,
785 replay_opened, replay_closed,
786 replay_cdata, rev_ctx,
789 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL,
792 handler->method = "REPORT";
793 handler->path = replay_target;
794 handler->body_delegate = create_replay_body;
795 handler->body_delegate_baton = rev_ctx;
796 handler->body_type = "text/xml";
798 handler->done_delegate = replay_done;
799 handler->done_delegate_baton = rev_ctx;
801 handler->custom_accept_encoding = TRUE;
802 handler->header_delegate = setup_headers;
803 handler->header_delegate_baton = rev_ctx;
805 rev_ctx->report_handler = handler;
806 svn_ra_serf__request_create(handler);
812 /* Run the serf loop. */
815 svn_error_t *err = svn_ra_serf__context_run_wait(&done, session,
820 svn_pool_destroy(subpool); /* Unregister all requests! */
821 return svn_error_trace(err);
825 /* The done handler of reports decrements active_reports when a report
826 is done. This same handler reports (fatal) report errors, so we can
830 svn_pool_destroy(subpool);
833 #undef MAX_OUTSTANDING_REQUESTS