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