replay.c revision 262253
1/*
2 * replay.c :  entry point for replay RA functions for ra_serf
3 *
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
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#include <apr_uri.h>
27#include <serf.h>
28
29#include "svn_pools.h"
30#include "svn_ra.h"
31#include "svn_dav.h"
32#include "svn_xml.h"
33#include "../libsvn_ra/ra_loader.h"
34#include "svn_config.h"
35#include "svn_delta.h"
36#include "svn_base64.h"
37#include "svn_path.h"
38#include "svn_private_config.h"
39
40#include "private/svn_string_private.h"
41
42#include "ra_serf.h"
43
44
45/*
46 * This enum represents the current state of our XML parsing.
47 */
48typedef enum replay_state_e {
49  NONE = 0,
50  REPORT,
51  OPEN_DIR,
52  ADD_DIR,
53  OPEN_FILE,
54  ADD_FILE,
55  DELETE_ENTRY,
56  APPLY_TEXTDELTA,
57  CHANGE_PROP
58} replay_state_e;
59
60typedef struct replay_info_t replay_info_t;
61
62struct replay_info_t {
63  apr_pool_t *pool;
64
65  void *baton;
66  svn_stream_t *stream;
67
68  replay_info_t *parent;
69};
70
71typedef svn_error_t *
72(*change_prop_t)(void *baton,
73                 const char *name,
74                 const svn_string_t *value,
75                 apr_pool_t *pool);
76
77typedef struct prop_info_t {
78  apr_pool_t *pool;
79
80  change_prop_t change;
81
82  const char *name;
83  svn_boolean_t del_prop;
84
85  svn_stringbuf_t *prop_value;
86
87  replay_info_t *parent;
88} prop_info_t;
89
90typedef struct replay_context_t {
91  apr_pool_t *src_rev_pool;
92  apr_pool_t *dst_rev_pool;
93
94  /* Are we done fetching this file? */
95  svn_boolean_t done;
96  svn_ra_serf__list_t **done_list;
97  svn_ra_serf__list_t done_item;
98
99  /* callback to get an editor */
100  svn_ra_replay_revstart_callback_t revstart_func;
101  svn_ra_replay_revfinish_callback_t revfinish_func;
102  void *replay_baton;
103
104  /* replay receiver function and baton */
105  const svn_delta_editor_t *editor;
106  void *editor_baton;
107
108  /* Path and revision used to filter replayed changes.  If
109     INCLUDE_PATH is non-NULL, REVISION is unnecessary and will not be
110     included in the replay REPORT.  (Because the REPORT is being
111     aimed an HTTP v2 revision resource.)  */
112  const char *include_path;
113  svn_revnum_t revision;
114
115  /* Information needed to create the replay report body */
116  svn_revnum_t low_water_mark;
117  svn_boolean_t send_deltas;
118
119  /* Target and revision to fetch revision properties on */
120  const char *revprop_target;
121  svn_revnum_t revprop_rev;
122
123  /* Revision properties for this revision. */
124  apr_hash_t *revs_props;
125  apr_hash_t *props;
126
127  /* Keep a reference to the XML parser ctx to report any errors. */
128  svn_ra_serf__xml_parser_t *parser_ctx;
129
130  /* Handlers for the PROPFIND and REPORT for the current revision. */
131  svn_ra_serf__handler_t *propfind_handler;
132  svn_ra_serf__handler_t *report_handler;
133
134} replay_context_t;
135
136
137static void *
138push_state(svn_ra_serf__xml_parser_t *parser,
139           replay_context_t *replay_ctx,
140           replay_state_e state)
141{
142  svn_ra_serf__xml_push_state(parser, state);
143
144  if (state == OPEN_DIR || state == ADD_DIR ||
145      state == OPEN_FILE || state == ADD_FILE)
146    {
147      replay_info_t *info;
148      apr_pool_t *pool = svn_pool_create(replay_ctx->dst_rev_pool);
149
150      info = apr_palloc(pool, sizeof(*info));
151
152      info->pool = pool;
153      info->parent = parser->state->private;
154      info->baton = NULL;
155      info->stream = NULL;
156
157      parser->state->private = info;
158    }
159  else if (state == CHANGE_PROP)
160    {
161      prop_info_t *info;
162      apr_pool_t *pool = svn_pool_create(replay_ctx->dst_rev_pool);
163
164      info = apr_pcalloc(pool, sizeof(*info));
165
166      info->pool = pool;
167      info->parent = parser->state->private;
168      info->prop_value = svn_stringbuf_create_empty(pool);
169
170      parser->state->private = info;
171    }
172
173  return parser->state->private;
174}
175
176static svn_error_t *
177start_replay(svn_ra_serf__xml_parser_t *parser,
178             svn_ra_serf__dav_props_t name,
179             const char **attrs,
180             apr_pool_t *scratch_pool)
181{
182  replay_context_t *ctx = parser->user_data;
183  replay_state_e state;
184
185  state = parser->state->current_state;
186
187  if (state == NONE &&
188      strcmp(name.name, "editor-report") == 0)
189    {
190      push_state(parser, ctx, REPORT);
191
192      /* Before we can continue, we need the revision properties. */
193      SVN_ERR_ASSERT(!ctx->propfind_handler || ctx->propfind_handler->done);
194
195      /* Create a pool for the commit editor. */
196      ctx->dst_rev_pool = svn_pool_create(ctx->src_rev_pool);
197
198      SVN_ERR(svn_ra_serf__select_revprops(&ctx->props,
199                                           ctx->revprop_target,
200                                           ctx->revprop_rev,
201                                           ctx->revs_props,
202                                           ctx->dst_rev_pool,
203                                           scratch_pool));
204
205      if (ctx->revstart_func)
206        {
207          SVN_ERR(ctx->revstart_func(ctx->revision, ctx->replay_baton,
208                                     &ctx->editor, &ctx->editor_baton,
209                                     ctx->props,
210                                     ctx->dst_rev_pool));
211        }
212    }
213  else if (state == REPORT &&
214           strcmp(name.name, "target-revision") == 0)
215    {
216      const char *rev;
217
218      rev = svn_xml_get_attr_value("rev", attrs);
219      if (!rev)
220        {
221          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
222                    _("Missing revision attr in target-revision element"));
223        }
224
225      SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
226                                               SVN_STR_TO_REV(rev),
227                                               scratch_pool));
228    }
229  else if (state == REPORT &&
230           strcmp(name.name, "open-root") == 0)
231    {
232      const char *rev;
233      replay_info_t *info;
234
235      rev = svn_xml_get_attr_value("rev", attrs);
236
237      if (!rev)
238        {
239          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
240                    _("Missing revision attr in open-root element"));
241        }
242
243      info = push_state(parser, ctx, OPEN_DIR);
244
245      SVN_ERR(ctx->editor->open_root(ctx->editor_baton,
246                                     SVN_STR_TO_REV(rev),
247                                     ctx->dst_rev_pool,
248                                     &info->baton));
249    }
250  else if ((state == OPEN_DIR || state == ADD_DIR) &&
251           strcmp(name.name, "delete-entry") == 0)
252    {
253      const char *file_name, *rev;
254      replay_info_t *info;
255
256      file_name = svn_xml_get_attr_value("name", attrs);
257      if (!file_name)
258        {
259          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
260                    _("Missing name attr in delete-entry element"));
261        }
262      rev = svn_xml_get_attr_value("rev", attrs);
263      if (!rev)
264        {
265          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
266                    _("Missing revision attr in delete-entry element"));
267        }
268
269      info = push_state(parser, ctx, DELETE_ENTRY);
270
271      SVN_ERR(ctx->editor->delete_entry(file_name, SVN_STR_TO_REV(rev),
272                                        info->baton, scratch_pool));
273
274      svn_ra_serf__xml_pop_state(parser);
275    }
276  else if ((state == OPEN_DIR || state == ADD_DIR) &&
277           strcmp(name.name, "open-directory") == 0)
278    {
279      const char *rev, *dir_name;
280      replay_info_t *info;
281
282      dir_name = svn_xml_get_attr_value("name", attrs);
283      if (!dir_name)
284        {
285          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
286                    _("Missing name attr in open-directory element"));
287        }
288      rev = svn_xml_get_attr_value("rev", attrs);
289      if (!rev)
290        {
291          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
292                    _("Missing revision attr in open-directory element"));
293        }
294
295      info = push_state(parser, ctx, OPEN_DIR);
296
297      SVN_ERR(ctx->editor->open_directory(dir_name, info->parent->baton,
298                                          SVN_STR_TO_REV(rev),
299                                          ctx->dst_rev_pool, &info->baton));
300    }
301  else if ((state == OPEN_DIR || state == ADD_DIR) &&
302           strcmp(name.name, "add-directory") == 0)
303    {
304      const char *dir_name, *copyfrom, *copyrev;
305      svn_revnum_t rev;
306      replay_info_t *info;
307
308      dir_name = svn_xml_get_attr_value("name", attrs);
309      if (!dir_name)
310        {
311          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
312                    _("Missing name attr in add-directory element"));
313        }
314      copyfrom = svn_xml_get_attr_value("copyfrom-path", attrs);
315      copyrev = svn_xml_get_attr_value("copyfrom-rev", attrs);
316
317      if (copyrev)
318        rev = SVN_STR_TO_REV(copyrev);
319      else
320        rev = SVN_INVALID_REVNUM;
321
322      info = push_state(parser, ctx, ADD_DIR);
323
324      SVN_ERR(ctx->editor->add_directory(dir_name, info->parent->baton,
325                                         copyfrom, rev,
326                                         ctx->dst_rev_pool, &info->baton));
327    }
328  else if ((state == OPEN_DIR || state == ADD_DIR) &&
329           strcmp(name.name, "close-directory") == 0)
330    {
331      replay_info_t *info = parser->state->private;
332
333      SVN_ERR(ctx->editor->close_directory(info->baton, scratch_pool));
334
335      svn_ra_serf__xml_pop_state(parser);
336
337      svn_pool_destroy(info->pool);
338    }
339  else if ((state == OPEN_DIR || state == ADD_DIR) &&
340           strcmp(name.name, "open-file") == 0)
341    {
342      const char *file_name, *rev;
343      replay_info_t *info;
344
345      file_name = svn_xml_get_attr_value("name", attrs);
346      if (!file_name)
347        {
348          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
349                    _("Missing name attr in open-file element"));
350        }
351      rev = svn_xml_get_attr_value("rev", attrs);
352      if (!rev)
353        {
354          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
355                    _("Missing revision attr in open-file element"));
356        }
357
358      info = push_state(parser, ctx, OPEN_FILE);
359
360      SVN_ERR(ctx->editor->open_file(file_name, info->parent->baton,
361                                     SVN_STR_TO_REV(rev),
362                                     info->pool, &info->baton));
363    }
364  else if ((state == OPEN_DIR || state == ADD_DIR) &&
365           strcmp(name.name, "add-file") == 0)
366    {
367      const char *file_name, *copyfrom, *copyrev;
368      svn_revnum_t rev;
369      replay_info_t *info;
370
371      file_name = svn_xml_get_attr_value("name", attrs);
372      if (!file_name)
373        {
374          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
375                    _("Missing name attr in add-file element"));
376        }
377      copyfrom = svn_xml_get_attr_value("copyfrom-path", attrs);
378      copyrev = svn_xml_get_attr_value("copyfrom-rev", attrs);
379
380      info = push_state(parser, ctx, ADD_FILE);
381
382      if (copyrev)
383        rev = SVN_STR_TO_REV(copyrev);
384      else
385        rev = SVN_INVALID_REVNUM;
386
387      SVN_ERR(ctx->editor->add_file(file_name, info->parent->baton,
388                                    copyfrom, rev,
389                                    info->pool, &info->baton));
390    }
391  else if ((state == OPEN_FILE || state == ADD_FILE) &&
392           strcmp(name.name, "apply-textdelta") == 0)
393    {
394      const char *checksum;
395      replay_info_t *info;
396      svn_txdelta_window_handler_t textdelta;
397      void *textdelta_baton;
398      svn_stream_t *delta_stream;
399
400      info = push_state(parser, ctx, APPLY_TEXTDELTA);
401
402      checksum = svn_xml_get_attr_value("checksum", attrs);
403      if (checksum)
404        {
405          checksum = apr_pstrdup(info->pool, checksum);
406        }
407
408      SVN_ERR(ctx->editor->apply_textdelta(info->baton, checksum,
409                                           info->pool,
410                                           &textdelta,
411                                           &textdelta_baton));
412
413      delta_stream = svn_txdelta_parse_svndiff(textdelta, textdelta_baton,
414                                               TRUE, info->pool);
415      info->stream = svn_base64_decode(delta_stream, info->pool);
416    }
417  else if ((state == OPEN_FILE || state == ADD_FILE) &&
418           strcmp(name.name, "close-file") == 0)
419    {
420      replay_info_t *info = parser->state->private;
421      const char *checksum;
422
423      checksum = svn_xml_get_attr_value("checksum", attrs);
424
425      SVN_ERR(ctx->editor->close_file(info->baton, checksum, scratch_pool));
426
427      svn_ra_serf__xml_pop_state(parser);
428
429      svn_pool_destroy(info->pool);
430    }
431  else if (((state == OPEN_FILE || state == ADD_FILE) &&
432            strcmp(name.name, "change-file-prop") == 0) ||
433           ((state == OPEN_DIR || state == ADD_DIR) &&
434            strcmp(name.name, "change-dir-prop") == 0))
435    {
436      const char *prop_name;
437      prop_info_t *info;
438
439      prop_name = svn_xml_get_attr_value("name", attrs);
440      if (!prop_name)
441        {
442          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
443                                   _("Missing name attr in %s element"),
444                                   name.name);
445        }
446
447      info = push_state(parser, ctx, CHANGE_PROP);
448
449
450      if (svn_xml_get_attr_value("del", attrs))
451        info->del_prop = TRUE;
452      else
453        info->del_prop = FALSE;
454
455      info->name = apr_pstrdup(info->pool, prop_name);
456      if (state == OPEN_FILE || state == ADD_FILE)
457        {
458          info->change = ctx->editor->change_file_prop;
459        }
460      else
461        {
462          info->change = ctx->editor->change_dir_prop;
463        }
464
465    }
466
467  return SVN_NO_ERROR;
468}
469
470static svn_error_t *
471end_replay(svn_ra_serf__xml_parser_t *parser,
472           svn_ra_serf__dav_props_t name,
473           apr_pool_t *scratch_pool)
474{
475  replay_context_t *ctx = parser->user_data;
476  replay_state_e state;
477
478  state = parser->state->current_state;
479
480  if (state == REPORT &&
481      strcmp(name.name, "editor-report") == 0)
482    {
483      svn_ra_serf__xml_pop_state(parser);
484      if (ctx->revfinish_func)
485        {
486          SVN_ERR(ctx->revfinish_func(ctx->revision, ctx->replay_baton,
487                                      ctx->editor, ctx->editor_baton,
488                                      ctx->props,
489                                      ctx->dst_rev_pool));
490        }
491      svn_pool_destroy(ctx->dst_rev_pool);
492    }
493  else if (state == OPEN_DIR && strcmp(name.name, "open-directory") == 0)
494    {
495      /* Don't do anything. */
496    }
497  else if (state == ADD_DIR && strcmp(name.name, "add-directory") == 0)
498    {
499      /* Don't do anything. */
500    }
501  else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
502    {
503      /* Don't do anything. */
504    }
505  else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
506    {
507      /* Don't do anything. */
508    }
509  else if ((state == OPEN_FILE || state == ADD_FILE) &&
510           strcmp(name.name, "close-file") == 0)
511    {
512      /* Don't do anything. */
513    }
514  else if ((state == APPLY_TEXTDELTA) &&
515           strcmp(name.name, "apply-textdelta") == 0)
516    {
517      replay_info_t *info = parser->state->private;
518      SVN_ERR(svn_stream_close(info->stream));
519      svn_ra_serf__xml_pop_state(parser);
520    }
521  else if (state == CHANGE_PROP &&
522           (strcmp(name.name, "change-file-prop") == 0 ||
523            strcmp(name.name, "change-dir-prop") == 0))
524    {
525      prop_info_t *info = parser->state->private;
526      const svn_string_t *prop_val;
527
528      if (info->del_prop)
529        {
530          prop_val = NULL;
531        }
532      else
533        {
534          const svn_string_t *morph;
535
536          morph = svn_stringbuf__morph_into_string(info->prop_value);
537#ifdef SVN_DEBUG
538          info->prop_value = NULL;  /* morph killed the stringbuf.  */
539#endif
540
541          prop_val = svn_base64_decode_string(morph, info->pool);
542        }
543
544      SVN_ERR(info->change(info->parent->baton, info->name, prop_val,
545                           info->parent->pool));
546      svn_ra_serf__xml_pop_state(parser);
547
548      svn_pool_destroy(info->pool);
549    }
550
551  return SVN_NO_ERROR;
552}
553
554static svn_error_t *
555cdata_replay(svn_ra_serf__xml_parser_t *parser,
556             const char *data,
557             apr_size_t len,
558             apr_pool_t *scratch_pool)
559{
560  replay_context_t *replay_ctx = parser->user_data;
561  replay_state_e state;
562
563  UNUSED_CTX(replay_ctx);
564
565  state = parser->state->current_state;
566
567  if (state == APPLY_TEXTDELTA)
568    {
569      replay_info_t *info = parser->state->private;
570      apr_size_t written;
571
572      written = len;
573
574      SVN_ERR(svn_stream_write(info->stream, data, &written));
575
576      if (written != len)
577        return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
578                                _("Error writing stream: unexpected EOF"));
579    }
580  else if (state == CHANGE_PROP)
581    {
582      prop_info_t *info = parser->state->private;
583
584      svn_stringbuf_appendbytes(info->prop_value, data, len);
585    }
586
587  return SVN_NO_ERROR;
588}
589
590static svn_error_t *
591create_replay_body(serf_bucket_t **bkt,
592                   void *baton,
593                   serf_bucket_alloc_t *alloc,
594                   apr_pool_t *pool)
595{
596  replay_context_t *ctx = baton;
597  serf_bucket_t *body_bkt;
598
599  body_bkt = serf_bucket_aggregate_create(alloc);
600
601  svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
602                                    "S:replay-report",
603                                    "xmlns:S", SVN_XML_NAMESPACE,
604                                    NULL);
605
606  /* If we have a non-NULL include path, we add it to the body and
607     omit the revision; otherwise, the reverse. */
608  if (ctx->include_path)
609    {
610      svn_ra_serf__add_tag_buckets(body_bkt,
611                                   "S:include-path",
612                                   ctx->include_path,
613                                   alloc);
614    }
615  else
616    {
617      svn_ra_serf__add_tag_buckets(body_bkt,
618                                   "S:revision",
619                                   apr_ltoa(ctx->src_rev_pool, ctx->revision),
620                                   alloc);
621    }
622  svn_ra_serf__add_tag_buckets(body_bkt,
623                               "S:low-water-mark",
624                               apr_ltoa(ctx->src_rev_pool, ctx->low_water_mark),
625                               alloc);
626
627  svn_ra_serf__add_tag_buckets(body_bkt,
628                               "S:send-deltas",
629                               apr_ltoa(ctx->src_rev_pool, ctx->send_deltas),
630                               alloc);
631
632  svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "S:replay-report");
633
634  *bkt = body_bkt;
635  return SVN_NO_ERROR;
636}
637
638svn_error_t *
639svn_ra_serf__replay(svn_ra_session_t *ra_session,
640                    svn_revnum_t revision,
641                    svn_revnum_t low_water_mark,
642                    svn_boolean_t send_deltas,
643                    const svn_delta_editor_t *editor,
644                    void *edit_baton,
645                    apr_pool_t *pool)
646{
647  replay_context_t *replay_ctx;
648  svn_ra_serf__session_t *session = ra_session->priv;
649  svn_ra_serf__handler_t *handler;
650  svn_ra_serf__xml_parser_t *parser_ctx;
651  svn_error_t *err;
652  const char *report_target;
653
654  SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
655
656  replay_ctx = apr_pcalloc(pool, sizeof(*replay_ctx));
657  replay_ctx->src_rev_pool = pool;
658  replay_ctx->editor = editor;
659  replay_ctx->editor_baton = edit_baton;
660  replay_ctx->done = FALSE;
661  replay_ctx->revision = revision;
662  replay_ctx->low_water_mark = low_water_mark;
663  replay_ctx->send_deltas = send_deltas;
664  replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
665
666  handler = apr_pcalloc(pool, sizeof(*handler));
667
668  handler->handler_pool = pool;
669  handler->method = "REPORT";
670  handler->path = session->session_url.path;
671  handler->body_delegate = create_replay_body;
672  handler->body_delegate_baton = replay_ctx;
673  handler->body_type = "text/xml";
674  handler->conn = session->conns[0];
675  handler->session = session;
676
677  parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
678
679  parser_ctx->pool = pool;
680  parser_ctx->user_data = replay_ctx;
681  parser_ctx->start = start_replay;
682  parser_ctx->end = end_replay;
683  parser_ctx->cdata = cdata_replay;
684  parser_ctx->done = &replay_ctx->done;
685
686  handler->response_handler = svn_ra_serf__handle_xml_parser;
687  handler->response_baton = parser_ctx;
688
689  /* This is only needed to handle errors during XML parsing. */
690  replay_ctx->parser_ctx = parser_ctx;
691  replay_ctx->report_handler = handler; /* unused */
692
693  svn_ra_serf__request_create(handler);
694
695  err = svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool);
696
697  SVN_ERR(svn_error_compose_create(
698              svn_ra_serf__error_on_status(handler->sline,
699                                           handler->path,
700                                           handler->location),
701              err));
702
703  return SVN_NO_ERROR;
704}
705
706/* The maximum number of outstanding requests at any time. When this
707 * number is reached, ra_serf will stop sending requests until
708 * responses on the previous requests are received and handled.
709 *
710 * Some observations about serf which lead us to the current value.
711 * ----------------------------------------------------------------
712 *
713 * We aim to keep serf's outgoing queue filled with enough requests so
714 * the network bandwidth and server capacity is used
715 * optimally. Originally we used 5 as the max. number of outstanding
716 * requests, but this turned out to be too low.
717 *
718 * Serf doesn't exit out of the svn_ra_serf__context_run_wait loop as long as
719 * it has data to send or receive. With small responses (revs of a few
720 * kB), serf doesn't come out of this loop at all. So with
721 * MAX_OUTSTANDING_REQUESTS set to a low number, there's a big chance
722 * that serf handles those requests completely in its internal loop,
723 * and only then gives us a chance to create new requests. This
724 * results in hiccups, slowing down the whole process.
725 *
726 * With a larger MAX_OUTSTANDING_REQUESTS, like 100 or more, there's
727 * more chance that serf can come out of its internal loop so we can
728 * replenish the outgoing request queue.  There's no real disadvantage
729 * of using a large number here, besides the memory used to store the
730 * message, parser and handler objects (approx. 250 bytes).
731 *
732 * In my test setup peak performance was reached at max. 30-35
733 * requests. So I added a small margin and chose 50.
734 */
735#define MAX_OUTSTANDING_REQUESTS 50
736
737svn_error_t *
738svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
739                          svn_revnum_t start_revision,
740                          svn_revnum_t end_revision,
741                          svn_revnum_t low_water_mark,
742                          svn_boolean_t send_deltas,
743                          svn_ra_replay_revstart_callback_t revstart_func,
744                          svn_ra_replay_revfinish_callback_t revfinish_func,
745                          void *replay_baton,
746                          apr_pool_t *pool)
747{
748  svn_ra_serf__session_t *session = ra_session->priv;
749  svn_revnum_t rev = start_revision;
750  const char *report_target;
751  int active_reports = 0;
752  const char *include_path;
753
754  SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
755
756  /* Prior to 1.8, mod_dav_svn expect to get replay REPORT requests
757     aimed at the session URL.  But that's incorrect -- these reports
758     aren't about specific resources -- they are above revisions.  The
759     path-based filtering offered by this API is just that: a filter
760     applied to the full set of changes made in the revision.  As
761     such, the correct target for these REPORT requests is the "me
762     resource" (or, pre-http-v2, the default VCC).
763
764     Our server should have told us if it supported this protocol
765     correction.  If so, we aimed our report at the correct resource
766     and include the filtering path as metadata within the report
767     body.  Otherwise, we fall back to the pre-1.8 behavior and just
768     wish for the best.
769
770     See issue #4287:
771     http://subversion.tigris.org/issues/show_bug.cgi?id=4287
772  */
773  if (session->supports_rev_rsrc_replay)
774    {
775      SVN_ERR(svn_ra_serf__get_relative_path(&include_path,
776                                             session->session_url.path,
777                                             session, session->conns[0],
778                                             pool));
779    }
780  else
781    {
782      include_path = NULL;
783    }
784
785  while (active_reports || rev <= end_revision)
786    {
787      svn_ra_serf__list_t *done_list;
788      svn_ra_serf__list_t *done_reports = NULL;
789      replay_context_t *replay_ctx;
790
791      if (session->cancel_func)
792        SVN_ERR(session->cancel_func(session->cancel_baton));
793
794      /* Send pending requests, if any. Limit the number of outstanding
795         requests to MAX_OUTSTANDING_REQUESTS. */
796      if (rev <= end_revision  && active_reports < MAX_OUTSTANDING_REQUESTS)
797        {
798          svn_ra_serf__handler_t *handler;
799          svn_ra_serf__xml_parser_t *parser_ctx;
800          apr_pool_t *ctx_pool = svn_pool_create(pool);
801          const char *replay_target;
802
803          replay_ctx = apr_pcalloc(ctx_pool, sizeof(*replay_ctx));
804          replay_ctx->src_rev_pool = ctx_pool;
805          replay_ctx->revstart_func = revstart_func;
806          replay_ctx->revfinish_func = revfinish_func;
807          replay_ctx->replay_baton = replay_baton;
808          replay_ctx->done = FALSE;
809          replay_ctx->include_path = include_path;
810          replay_ctx->revision = rev;
811          replay_ctx->low_water_mark = low_water_mark;
812          replay_ctx->send_deltas = send_deltas;
813          replay_ctx->done_item.data = replay_ctx;
814
815          /* Request all properties of a certain revision. */
816          replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
817
818          if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
819            {
820              replay_ctx->revprop_target = apr_psprintf(pool, "%s/%ld",
821                                                        session->rev_stub, rev);
822              replay_ctx->revprop_rev = SVN_INVALID_REVNUM;
823            }
824          else
825            {
826              replay_ctx->revprop_target = report_target;
827              replay_ctx->revprop_rev = rev;
828            }
829
830          SVN_ERR(svn_ra_serf__deliver_props(&replay_ctx->propfind_handler,
831                                             replay_ctx->revs_props, session,
832                                             session->conns[0],
833                                             replay_ctx->revprop_target,
834                                             replay_ctx->revprop_rev,
835                                             "0", all_props,
836                                             NULL,
837                                             replay_ctx->src_rev_pool));
838
839          /* Spin up the serf request for the PROPFIND.  */
840          svn_ra_serf__request_create(replay_ctx->propfind_handler);
841
842          /* Send the replay REPORT request. */
843          if (session->supports_rev_rsrc_replay)
844            {
845              replay_target = apr_psprintf(pool, "%s/%ld",
846                                           session->rev_stub, rev);
847            }
848          else
849            {
850              replay_target = session->session_url.path;
851            }
852
853          handler = apr_pcalloc(replay_ctx->src_rev_pool, sizeof(*handler));
854
855          handler->handler_pool = replay_ctx->src_rev_pool;
856          handler->method = "REPORT";
857          handler->path = replay_target;
858          handler->body_delegate = create_replay_body;
859          handler->body_delegate_baton = replay_ctx;
860          handler->conn = session->conns[0];
861          handler->session = session;
862
863          parser_ctx = apr_pcalloc(replay_ctx->src_rev_pool,
864                                   sizeof(*parser_ctx));
865
866          /* Setup the XML parser context.
867             Because we have not one but a list of requests, the 'done' property
868             on the replay_ctx is not of much use. Instead, use 'done_list'.
869             On each handled response (succesfully or not), the parser will add
870             done_item to done_list, so by keeping track of the state of
871             done_list we know how many requests have been handled completely.
872          */
873          parser_ctx->pool = replay_ctx->src_rev_pool;
874          parser_ctx->user_data = replay_ctx;
875          parser_ctx->start = start_replay;
876          parser_ctx->end = end_replay;
877          parser_ctx->cdata = cdata_replay;
878          parser_ctx->done = &replay_ctx->done;
879          parser_ctx->done_list = &done_reports;
880          parser_ctx->done_item = &replay_ctx->done_item;
881          handler->response_handler = svn_ra_serf__handle_xml_parser;
882          handler->response_baton = parser_ctx;
883          replay_ctx->report_handler = handler;
884
885          /* This is only needed to handle errors during XML parsing. */
886          replay_ctx->parser_ctx = parser_ctx;
887
888          svn_ra_serf__request_create(handler);
889
890          rev++;
891          active_reports++;
892        }
893
894      /* Run the serf loop. */
895      SVN_ERR(svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool));
896
897      /* Substract the number of completely handled responses from our
898         total nr. of open requests', so we'll know when to stop this loop.
899         Since the message is completely handled, we can destroy its pool. */
900      done_list = done_reports;
901      while (done_list)
902        {
903          replay_context_t *ctx = (replay_context_t *)done_list->data;
904          svn_ra_serf__handler_t *done_handler = ctx->report_handler;
905
906          done_list = done_list->next;
907          SVN_ERR(svn_ra_serf__error_on_status(done_handler->sline,
908                                               done_handler->path,
909                                               done_handler->location));
910          svn_pool_destroy(ctx->src_rev_pool);
911          active_reports--;
912        }
913
914      done_reports = NULL;
915    }
916
917  return SVN_NO_ERROR;
918}
919#undef MAX_OUTSTANDING_REQUESTS
920