On 05.04.21 20:19, Nathan Hartman wrote:
On Mon, Apr 5, 2021 at 6:00 AM Stefan Fuhrmann <stef...@apache.org <mailto:stef...@apache.org>> wrote:

    Hi all,

    Way back in 2014, I started work on an SVN equivalent to apr_thread_pool
    and came back to it recently.  The key features are output and callbacks
    happen in the same order as in sequential code, same for any svn_error_t
    raised, and a low-overhead single-threaded code path.

    Now, there is a working prototype and I used it in
    svn_wc__internal_walk_status.
       Integration was simple enough and scaling is excellent, both for cold 
and hot
    file caches.  I'm on staycation this week, so I would like to bring the code
    to trunk (with proper documentation and all).  But beyond that, I don't know
    how responsive I can be in case things go wrong.  So, I would very much like
    somebody dedicate time to reviewing it this week, give feedback etc.

    Any takers?

    -- Stefan^2.



Now is actually a good time to introduce this because we have other heavy duty optimizations on trunk, and it would be a good thing if we could get back on track with regular feature releases between LTS releases, with something "marketable" in them, which I think this qualifies as. 1.15 could be summed up as a "faster performance" release.

That is good to hear.  While looking for 'svn st' performance, I also identified
a couple of redundant queries in wc_* as well as a big-O blunder in APR (will
send them a patch tomorrow).  Downside is that the attached patches are not
particularly clean.

The only reason I can think of why anyone might object is if the changes add new dependencies.

None of that.  The trickiest thing is how to feed the number of workers to use
into the function.  I have no real opinion on that and hard-coded it in the
svn_task__run() call instead of bumping the status API and adding a new CLI
option.

If you could post a patch of what you have so far, even if incomplete, it will help us see how big (or small) the proposed changes are and what they entail.

See attachment (I may have used a somewhat outdated trunk).

tasks-prepwork.patch refactors some utility code to make it reusable.
tasks-main.patch is the new svn_task__t API, implementation and bits of testing.
tasks-used*.patch is calling the new API from svn_wc__internal_walk_status plus
optimizations in wc_*.*

The patches are only to give you an idea of the scope of the change; they lack
almost all documentation.  I plan to go for a simpler first integration, i.e.
keep wc_*.* as is and possibly reduce the code churn in status.c.

I'm willing to give the code an extra pair of eyeballs, build and test, and if it gets the blessing of the current test suite I'm willing to build trunk with the new code and try it as my daily driver to put it through its paces in real world (client side) usage.

Thank you very much!  I hope the patches don't miss anything and you can compile
and run them as-is.

If you're concerned that you won't be able to work on it after this week, you could opt to put it on a branch, but I understand that it might languish there. Your call...

I'll go for trunk.  Committing the content of the 2 first patches as a series
of nice commits should be close to zero risk.  Depending on the feedback, we
can then decide how / when / if to continue with touching svn_wc.

-- Stefan^2.


Index: include/private/svn_task.h
===================================================================
--- include/private/svn_task.h	(nonexistent)
+++ include/private/svn_task.h	(working copy)
@@ -0,0 +1,89 @@
+/* svn_task.h : Interface to the parallel task handling.
+ *
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_TASK_H
+#define SVN_TASK_H
+
+#include "svn_pools.h"
+#include "svn_error.h"
+
+typedef struct svn_task__t svn_task__t;
+
+typedef svn_error_t *(*svn_task__process_func_t)(
+  void **result,
+  svn_task__t *task,
+  void *thread_context,
+  void *process_baton,
+  svn_cancel_func_t cancel_func,
+  void *cancel_baton,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool);
+
+typedef svn_error_t *(*svn_task__output_func_t)(
+  svn_task__t *task,
+  void *result,
+  void *output_baton,
+  svn_cancel_func_t cancel_func,
+  void *cancel_baton,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool);
+
+typedef svn_error_t *(*svn_task__thread_context_constructor_t)(
+  void **thread_context,
+  void *baton,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool);
+
+apr_pool_t *svn_task__create_pool(
+  svn_task__t *parent);
+
+svn_error_t *svn_task__add(
+  svn_task__t **task,
+  svn_task__t *parent,
+  apr_pool_t *process_pool,
+  void *partial_output,
+  svn_task__process_func_t process_func,
+  void *process_baton,
+  svn_task__output_func_t output_func,
+  void *output_baton);
+
+svn_error_t *svn_task__add_similar(
+  svn_task__t **task,
+  svn_task__t *parent,
+  apr_pool_t *process_pool,
+  void *partial_output,
+  void *process_baton);
+
+svn_error_t *svn_task__run(
+  apr_int32_t thread_count,
+  svn_task__process_func_t process_func,
+  void *process_baton,
+  svn_task__output_func_t output_func,
+  void *output_baton,
+  svn_task__thread_context_constructor_t context_constructor,
+  void *context_baton,
+  svn_cancel_func_t cancel_func,
+  void *cancel_baton,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool);
+
+#endif

Property changes on: include/private/svn_task.h
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: libsvn_subr/task.c
===================================================================
--- libsvn_subr/task.c	(nonexistent)
+++ libsvn_subr/task.c	(working copy)
@@ -0,0 +1,751 @@
+/* task.c : Implement the parallel task execution machine.
+ *
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+#include "private/svn_task.h"
+
+#include <assert.h>
+#include <apr_thread_proc.h>
+
+#include "svn_sorts.h"
+#include "svn_time.h"
+
+#include "private/svn_atomic.h"
+#include "private/svn_thread_cond.h"
+
+typedef struct root_t
+{
+  svn_mutex__t *mutex;
+  svn_thread_cond__t *worker_wakeup;
+  svn_thread_cond__t *task_processed;
+
+  svn_task__t *task;
+
+  apr_pool_t *task_pool;
+  apr_pool_t *process_pool;
+  apr_pool_t *output_pool;
+
+  svn_mutex__t *task_alloc_mutex;
+
+  svn_task__thread_context_constructor_t context_constructor;
+  void *context_baton;
+
+  svn_atomic_t terminate;
+
+} root_t;
+
+typedef struct output_t
+{
+  void *output;
+  svn_error_t *error;
+  void *prior_parent_output;
+  svn_boolean_t has_sub_results;
+
+  apr_pool_t *pool;
+
+} output_t;
+
+typedef struct callbacks_t
+{
+  svn_task__process_func_t process_func;
+  svn_task__output_func_t output_func;
+  void *output_baton;
+} callbacks_t;
+
+struct svn_task__t
+{
+  root_t *root;
+
+  svn_task__t *parent;
+
+  svn_task__t *first_sub;
+  svn_task__t *last_sub;
+  svn_task__t *next;
+
+  apr_size_t depth;
+  apr_size_t sub_task_idx;
+  svn_task__t *first_ready;
+  svn_task__t *first_ready_sub_task;
+
+  svn_task__t *first_unprocessed;
+
+  callbacks_t *callbacks;
+  void *process_baton;
+  apr_pool_t *process_pool;
+  output_t *output;
+};
+
+static apr_status_t
+root_cleanup(void *baton)
+{
+  root_t *root = baton;
+  svn_pool_destroy(root->task_pool);
+  svn_pool_destroy(root->process_pool);
+  svn_pool_destroy(root->output_pool);
+
+  return APR_SUCCESS;
+}
+
+apr_pool_t *svn_task__create_pool(
+  svn_task__t *parent)
+{
+  return svn_pool_create(parent->root->process_pool);
+}
+
+static svn_error_t *add_new_task(svn_task__t *task)
+{
+  svn_task__t *current, *parent;
+  apr_uint32_t count;
+
+  if (task->parent->last_sub)
+    {
+      assert(task->parent->last_sub != task);
+      task->parent->last_sub->next = task;
+      task->sub_task_idx = task->parent->last_sub->sub_task_idx + 1;
+    }
+
+  if (task->parent->first_unprocessed == NULL)
+    task->parent->first_unprocessed = task;
+    
+  task->parent->last_sub = task;
+  if (!task->parent->first_sub)
+    task->parent->first_sub = task;
+
+  for (current = task, parent = task->parent;
+       parent && (parent->first_ready != parent) &&
+       (   !parent->first_ready_sub_task
+        || parent->first_ready_sub_task->sub_task_idx >= current->sub_task_idx);
+       current = parent, parent = parent->parent)
+    {
+      parent->first_ready_sub_task = current;
+      parent->first_ready = current->first_ready;
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static output_t *ensure_output(svn_task__t *task)
+{
+  if (!task->output)
+    {
+      apr_pool_t * output_pool = svn_pool_create(task->root->output_pool);
+      task->output = apr_pcalloc(output_pool, sizeof(*task->output));
+      task->output->pool = output_pool;
+    }
+
+  return task->output;
+}
+
+static svn_error_t *alloc_task(svn_task__t **result, apr_pool_t *pool)
+{
+  *result = apr_pcalloc(pool, sizeof(**result));
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *alloc_callbacks(callbacks_t **result, apr_pool_t *pool)
+{
+  *result = apr_pcalloc(pool, sizeof(**result));
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *add_task(
+  svn_task__t **task,
+  svn_task__t *parent,
+  apr_pool_t *process_pool,
+  void *partial_output,
+  callbacks_t *callbacks,
+  void *process_baton)
+{
+  svn_task__t *result;
+  SVN_MUTEX__WITH_LOCK(parent->root->task_alloc_mutex,
+                       alloc_task(&result, parent->root->task_pool));
+
+  result->root = parent->root;
+  result->process_baton = process_baton;
+  result->process_pool = process_pool;
+
+  result->parent = parent;
+  if (partial_output && parent->callbacks->output_func)
+    {
+      ensure_output(parent)->has_sub_results = TRUE;
+      ensure_output(result)->prior_parent_output = partial_output;
+    }
+
+  result->first_ready = result;
+  result->depth = parent->depth + 1;
+  result->callbacks = callbacks;
+
+  SVN_MUTEX__WITH_LOCK(result->root->mutex, add_new_task(result));
+  if (result->root->worker_wakeup)
+    SVN_ERR(svn_thread_cond__broadcast(result->root->worker_wakeup));
+
+  *task = result;
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_task__add(
+  svn_task__t **task,
+  svn_task__t *parent,
+  apr_pool_t *process_pool,
+  void *partial_output,
+  svn_task__process_func_t process_func,
+  void *process_baton,
+  svn_task__output_func_t output_func,
+  void *output_baton)
+{
+  callbacks_t *callbacks;
+  SVN_MUTEX__WITH_LOCK(parent->root->task_alloc_mutex,
+                       alloc_callbacks(&callbacks, parent->root->task_pool));
+
+  callbacks->process_func = process_func;
+  callbacks->output_func = output_func;
+  callbacks->output_baton = output_baton;
+
+  return svn_error_trace(add_task(task, parent, process_pool, partial_output,
+                                  callbacks, process_baton));
+}
+
+svn_error_t* svn_task__add_similar(
+  svn_task__t** task,
+  svn_task__t* parent,
+  apr_pool_t *process_pool,
+  void* partial_output,
+  void* process_baton)
+{
+  return svn_error_trace(add_task(task, parent, process_pool, partial_output,
+                                  parent->callbacks, process_baton));
+}
+
+static svn_task__t *next_ready(svn_task__t *task)
+{
+  for (; task; task = task->next)
+    if (task->first_ready)
+      break;
+
+  return task;
+}
+
+static svn_task__t *next_unprocessed(svn_task__t *task)
+{
+  for (; task; task = task->next)
+    if (task->first_ready == task)
+      break;
+
+  return task;
+}
+
+static void unready_task(svn_task__t *task)
+{
+  assert(task->first_ready == task);
+ 
+  svn_task__t *parent, *current;
+  svn_task__t *first_ready_sub_task = next_ready(task->first_sub);
+  svn_task__t *first_ready = first_ready_sub_task
+                           ? first_ready_sub_task->first_ready
+                           : NULL;
+
+  task->first_ready_sub_task = first_ready_sub_task;
+  task->first_ready = first_ready;
+
+  for (current = task, parent = task->parent;
+       parent && (parent->first_ready == task);
+       current = parent, parent = parent->parent)
+    {
+      if (!first_ready)
+        {
+          first_ready_sub_task = next_ready(current->next);
+          first_ready = first_ready_sub_task
+                      ? first_ready_sub_task->first_ready
+                      : NULL;
+        }
+      else
+        {
+          first_ready_sub_task = current;
+        }
+
+      parent->first_ready_sub_task = first_ready_sub_task;
+      parent->first_ready = first_ready;
+    }
+
+  if (task->parent && task->parent->first_unprocessed == task)
+    task->parent->first_unprocessed = next_unprocessed(task->next);
+}
+
+static svn_error_t *remove_task(svn_task__t *task)
+{
+  svn_task__t *parent = task->parent;
+  if (parent)
+    {
+      if (parent->first_sub == task)
+        parent->first_sub = task->next;
+      if (parent->last_sub == task)
+        parent->last_sub = NULL;
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *next_task(svn_task__t **task,
+                              root_t *root,
+                              apr_pool_t *scratch_pool)
+{
+  while (TRUE)
+    {
+      if (svn_atomic_read(&root->terminate))
+        {
+          *task = NULL;
+          return SVN_NO_ERROR;
+        }
+
+      if (root->task->first_ready)
+        {
+          svn_task__t *current = root->task->first_ready;
+          unready_task(current);
+          *task = current;
+
+          return SVN_NO_ERROR;
+        }
+      
+      SVN_ERR(svn_thread_cond__wait(root->worker_wakeup, root->mutex));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *worker_cancelled(void *baton)
+{
+  root_t *root = baton;
+  return svn_atomic_read(&root->terminate)
+       ? svn_error_create(SVN_ERR_CANCELLED, NULL, NULL)
+       : SVN_NO_ERROR;
+}
+
+svn_boolean_t is_conflicted(const svn_task__t *task)
+{
+  if (task->first_sub != task->first_ready)
+    return TRUE;
+
+  if (task->first_ready == NULL && task->parent && task->parent->first_ready
+      && task->parent->first_ready->sub_task_idx != task->sub_task_idx + 1)
+    return TRUE;
+
+  return FALSE;
+}
+
+static void set_processed(svn_task__t *task)
+{
+  task->process_pool = NULL;
+}
+
+static svn_boolean_t is_processed(svn_task__t *task)
+{
+  return task->process_pool == NULL;
+}
+
+static svn_error_t *set_processed_and_pick(svn_task__t **next_task,
+                                           svn_task__t *task)
+{
+  task->process_pool = NULL;
+  if (is_conflicted(task))
+    {
+      while (task->parent && task->parent->first_unprocessed)
+        task = task->parent;
+
+      task = task->first_unprocessed;
+    }
+  else
+    {
+      while (task->first_ready == NULL && task->parent)
+        task = task->parent;
+
+      task = task->first_ready;
+    }
+
+  if (task)
+    unready_task(task);
+
+  *next_task = task;
+  return SVN_NO_ERROR;
+}
+
+static void process(svn_task__t *task,
+                    void *thread_context,
+                    svn_cancel_func_t cancel_func,
+                    void *cancel_baton,
+                    apr_pool_t *scratch_pool)
+{
+  callbacks_t *callbacks = task->callbacks;
+
+  if (callbacks->process_func)
+    {
+      output_t *output = ensure_output(task);
+      output->error = callbacks->process_func(&output->output, task,
+                                              thread_context,
+                                              task->process_baton,
+                                              cancel_func, cancel_baton,
+                                              output->pool, scratch_pool);
+
+      if (!callbacks->output_func)
+        output->output = NULL;
+
+      if (   !output->error
+          && !output->output
+          && !output->prior_parent_output
+          && !output->has_sub_results)
+        {
+          svn_pool_destroy(output->pool);
+          task->output = NULL;
+        }
+    }
+
+  svn_pool_destroy(task->process_pool);
+}
+
+static svn_error_t *worker(root_t *root, apr_pool_t *scratch_pool)
+{
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+  svn_task__t *task = NULL;
+
+  /* The context may be quite complex, so we use the ITERPOOL to clean up any
+   * memory that was used temporarily during context creation. */
+  void *thread_context = NULL;
+  if (root->context_constructor)
+    SVN_ERR(root->context_constructor(&thread_context, root->context_baton,
+                                      scratch_pool, iterpool));
+
+  while (!svn_atomic_read(&root->terminate))
+    {
+      svn_pool_clear(iterpool);
+      if (!task)
+        {
+          SVN_ERR(svn_thread_cond__signal(root->task_processed));
+          SVN_MUTEX__WITH_LOCK(root->mutex, next_task(&task, root, iterpool));
+          if (!task)
+            break;
+        }
+
+      process(task, thread_context, worker_cancelled, root, iterpool);
+      SVN_MUTEX__WITH_LOCK(root->mutex,
+                           set_processed_and_pick(&task, task));
+    }
+
+  /* Cleanup. */
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
+static void * APR_THREAD_FUNC
+worker_thread(apr_thread_t *thread, void *data)
+{
+  /* Each thread uses a separate single-threaded pool tree for minimum overhead
+   */
+  apr_pool_t *pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+  apr_status_t result = APR_SUCCESS;
+  svn_error_t *err = worker(data, pool);
+  if (err)
+    {
+      result = err->apr_err;
+      svn_error_clear(err);
+    }
+  
+  svn_pool_destroy(pool);
+
+  /* End thread explicitly to prevent APR_INCOMPLETE return codes in
+     apr_thread_join(). */
+  apr_thread_exit(thread, result);
+  return NULL;
+}
+
+static svn_error_t *output_processed(
+  svn_task__t **task,
+  svn_cancel_func_t cancel_func,
+  void *cancel_baton,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool)
+{
+  svn_task__t *current = *task;
+  root_t *root = current->root;
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+  output_t *output;
+  callbacks_t *callbacks;
+  
+  while (current && is_processed(current))
+    {
+      svn_pool_clear(iterpool);
+      if (current->first_sub)
+        {
+          current = current->first_sub;
+          output = current->output;
+          callbacks = current->parent->callbacks;
+          if (output && output->prior_parent_output)
+            SVN_ERR(callbacks->output_func(
+                        current->parent, output->prior_parent_output,
+                        callbacks->output_baton,
+                        cancel_func, cancel_baton,
+                        result_pool, iterpool));
+        }
+      else
+        {
+          output_t *output = current->output;
+          if (output)
+            {
+              svn_error_t *err = output->error;
+              output->error = SVN_NO_ERROR;
+              SVN_ERR(err);
+              
+              callbacks = current->callbacks;
+              if (output->output)
+                SVN_ERR(callbacks->output_func(
+                            current, output->output,
+                            callbacks->output_baton,
+                            cancel_func, cancel_baton,
+                            result_pool, iterpool));
+            }
+
+          /* The output function may have added additional sub-tasks. */
+          if (!current->first_sub)
+            {
+              svn_task__t *to_delete = current;
+              current = to_delete->parent;
+              remove_task(to_delete);
+              if (output)
+                svn_pool_destroy(output->pool);
+            }
+        }
+    }
+
+  svn_pool_destroy(iterpool);
+  *task = current;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *wait_for_outputting_state(
+  svn_task__t *task,
+  apr_int32_t thread_count,
+  apr_array_header_t *threads)
+{
+  root_t* root = task->root;
+  while (TRUE)
+    {
+      if (is_processed(task))
+        return SVN_NO_ERROR;
+
+      /* Maybe spawn another worker thread because there are waiting tasks.
+        */
+      if (thread_count > threads->nelts)
+        {
+          apr_thread_t *thread;
+          apr_status_t status = apr_thread_create(&thread, NULL,
+                                                  worker_thread,
+                                                  root,
+                                                  threads->pool);
+          if (status)
+            return svn_error_wrap_apr(status,
+                                      "Creating worker thread failed");
+
+          APR_ARRAY_PUSH(threads, apr_thread_t *) = thread;
+        }
+
+      SVN_ERR(svn_thread_cond__wait(root->task_processed, root->mutex));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *send_terminate(root_t *root)
+{
+  svn_atomic_set(&root->terminate, TRUE);
+  return svn_thread_cond__broadcast(root->worker_wakeup);
+}
+
+static void free_sub_tasks(svn_task__t *task)
+{
+  while (task->first_sub)
+    free_sub_tasks(task->first_sub);
+
+  if (task->output)
+    svn_error_clear(task->output->error);
+
+  if (task->parent && task->parent->first_sub == task)
+    task->parent->first_sub = task->next;
+}
+
+static svn_error_t *execute_serially(
+  svn_task__t *task,
+  svn_cancel_func_t cancel_func,
+  void *cancel_baton,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool)
+{
+  svn_task__t *current = task;
+  root_t* root = task->root;
+  svn_error_t *task_err = SVN_NO_ERROR;
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+  /* The context may be quite complex, so we use the ITERPOOL to clean up any
+   * memory that was used temporarily during context creation. */
+  void *thread_context = NULL;
+  if (root->context_constructor)
+    SVN_ERR(root->context_constructor(&thread_context, root->context_baton,
+                                      scratch_pool, iterpool));
+
+  while (current && !task_err)
+    {
+      svn_pool_clear(iterpool);
+      unready_task(current);
+      process(current, thread_context, cancel_func, cancel_baton, iterpool);
+      set_processed(current);
+
+      task_err = output_processed(&current,
+                                  cancel_func, cancel_baton,
+                                  result_pool, scratch_pool);
+    }
+
+  /* */
+  free_sub_tasks(task);
+  svn_pool_destroy(iterpool);
+
+  /* Get rid of all remaining tasks. */
+  return svn_error_trace(task_err);
+}
+
+static svn_error_t *execute_concurrently(
+  svn_task__t *task,
+  apr_int32_t thread_count,
+  svn_cancel_func_t cancel_func,
+  void *cancel_baton,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool)
+{
+  int i;
+  svn_task__t *current = task;
+  root_t *root = task->root;
+  svn_error_t *task_err = SVN_NO_ERROR;
+  svn_error_t *sync_err = SVN_NO_ERROR;
+  apr_pool_t *thread_safe_pool = root->output_pool;
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+  apr_array_header_t *threads = apr_array_make(thread_safe_pool, thread_count,
+                                               sizeof(apr_thread_t *));
+
+  while (current && !task_err)
+    {
+      svn_pool_clear(iterpool);
+      SVN_MUTEX__WITH_LOCK(root->mutex,
+                           wait_for_outputting_state(current, thread_count,
+                                                     threads));
+      task_err = output_processed(&current,
+                                  cancel_func, cancel_baton,
+                                  result_pool, iterpool);
+    }
+  
+  /* Tell all worker threads to terminate. */
+  SVN_MUTEX__WITH_LOCK(root->mutex, send_terminate(root));
+
+  /* Wait for all threads to terminate. */
+  for (i = 0; i < threads->nelts; ++i)
+    {
+      apr_thread_t *thread = APR_ARRAY_IDX(threads, i, apr_thread_t *);
+      apr_status_t retval;
+      apr_status_t sync_status = apr_thread_join(&retval, thread);
+      
+      if (retval != APR_SUCCESS)
+        sync_err = svn_error_compose_create(sync_err,
+                        svn_error_wrap_apr(retval,
+                                          "Worker thread returned error"));
+        
+      if (sync_status != APR_SUCCESS)
+        sync_err = svn_error_compose_create(sync_err,
+                        svn_error_wrap_apr(sync_status,
+                                          "Worker thread join error"));
+    }
+  
+  /* */
+  svn_pool_destroy(iterpool);
+  if (!sync_err)
+    free_sub_tasks(task);
+
+  /* Get rid of all remaining tasks. */
+  return svn_error_trace(svn_error_compose_create(sync_err, task_err));
+}
+
+svn_error_t *svn_task__run(
+  apr_int32_t thread_count,
+  svn_task__process_func_t process_func,
+  void *process_baton,
+  svn_task__output_func_t output_func,
+  void *output_baton,
+  svn_task__thread_context_constructor_t context_constructor,
+  void *context_baton,
+  svn_cancel_func_t cancel_func,
+  void *cancel_baton,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool)
+{
+  root_t root;
+  callbacks_t callbacks;
+
+  SVN_ERR(svn_mutex__init(&root.mutex, TRUE, scratch_pool));
+  SVN_ERR(svn_mutex__init(&root.task_alloc_mutex, TRUE, scratch_pool));
+  SVN_ERR(svn_thread_cond__create(&root.task_processed, scratch_pool));
+  SVN_ERR(svn_thread_cond__create(&root.worker_wakeup, scratch_pool));
+
+  root.task_pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+  root.process_pool = apr_allocator_owner_get(svn_pool_create_allocator(TRUE));
+  root.output_pool = apr_allocator_owner_get(svn_pool_create_allocator(TRUE));
+
+  callbacks.process_func = process_func;
+  callbacks.output_func = output_func;
+  callbacks.output_baton = output_baton;
+  
+  root.task = apr_pcalloc(scratch_pool, sizeof(*root.task));
+  root.task->root = &root;
+  root.task->first_ready = root.task;
+  root.task->callbacks = &callbacks;
+  root.task->process_baton = process_baton;
+  root.task->process_pool = svn_pool_create(root.process_pool);
+
+  root.context_baton = context_baton;
+  root.context_constructor = context_constructor;
+  root.terminate = FALSE;
+
+  apr_pool_cleanup_register(scratch_pool, &root, root_cleanup,
+                            apr_pool_cleanup_null);
+
+#if APR_HAS_THREADS
+  if (thread_count > 1)
+    {
+      SVN_ERR(execute_concurrently(root.task, thread_count,
+                                   cancel_func, cancel_baton,
+                                   result_pool, scratch_pool));
+    }
+  else
+#endif
+   {
+     SVN_ERR(execute_serially(root.task,
+                              cancel_func, cancel_baton,
+                              result_pool, scratch_pool));
+   }    
+  
+  return SVN_NO_ERROR;
+}

Property changes on: libsvn_subr/task.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: tests/libsvn_subr/task-test.c
===================================================================
--- tests/libsvn_subr/task-test.c	(nonexistent)
+++ tests/libsvn_subr/task-test.c	(working copy)
@@ -0,0 +1,316 @@
+
+/*
+ * task-test.c:  a collection of svn_task__* tests
+ *
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+/* ====================================================================
+   To add tests, look toward the bottom of this file.
+
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <apr_pools.h>
+
+#include "../svn_test.h"
+
+#include "svn_sorts.h"
+#include "private/svn_atomic.h"
+#include "private/svn_task.h"
+
+static svn_error_t *
+test_null_task(apr_pool_t *pool)
+{
+  SVN_ERR(svn_task__run(1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                        pool, pool));
+  SVN_ERR(svn_task__run(2, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                        pool, pool));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+noop_process_func(void **result,
+                  svn_task__t *task,
+                  void *thread_context,
+                  void *process_baton,
+                  svn_cancel_func_t cancel_func,
+                  void *cancel_baton,
+                  apr_pool_t *result_pool,
+                  apr_pool_t *scratch_pool)
+{
+  *result = NULL;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+noop_output_func(svn_task__t *task,
+                 void *result,
+                 void *output_baton,
+                 svn_cancel_func_t cancel_func,
+                 void *cancel_baton,
+                 apr_pool_t *result_pool,
+                 apr_pool_t *scratch_pool)
+{
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+noop_thead_context_constructor(void **thread_context,
+                               void *baton,
+                               apr_pool_t *result_pool,
+                               apr_pool_t *scratch_pool)
+{
+  *thread_context = NULL;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+noop_cancel_func(void *baton)
+{
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_noop_task(apr_pool_t *pool)
+{
+  SVN_ERR(svn_task__run(1,
+                        noop_process_func, NULL,
+                        noop_output_func, NULL,
+                        noop_thead_context_constructor, NULL,
+                        noop_cancel_func, NULL, pool, pool));
+  SVN_ERR(svn_task__run(2,
+                        noop_process_func, NULL,
+                        noop_output_func, NULL,
+                        noop_thead_context_constructor, NULL,
+                        noop_cancel_func, NULL, pool, pool));
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+counter_func(void **result,
+             svn_task__t *task,
+             void *thread_context,
+             void *process_baton,
+             svn_cancel_func_t cancel_func,
+             void *cancel_baton,
+             apr_pool_t *result_pool,
+             apr_pool_t *scratch_pool)
+{
+  apr_int64_t value = *(apr_int64_t*)process_baton;
+
+  apr_pool_t *sub_task_pool;
+  apr_int64_t *partial_result;
+  apr_int64_t *partial_baton;
+  svn_task__t *sub_task;
+
+  if (value > 1)
+    {
+      partial_result = apr_palloc(result_pool, sizeof(partial_result));
+      *partial_result = 1;
+      value -= *partial_result;
+
+      sub_task_pool = svn_task__create_pool(task);
+
+      partial_baton = apr_palloc(sub_task_pool, sizeof(partial_baton));      
+      *partial_baton = MAX(1, value / 2);
+      value -= *partial_baton;
+
+      SVN_ERR(svn_task__add_similar(&sub_task, task, sub_task_pool, 
+                                    partial_result, partial_baton));
+    }
+
+  if (cancel_func)
+    SVN_ERR(cancel_func(cancel_baton));
+    
+  if (value > 1)
+    {
+      partial_result = apr_palloc(result_pool, sizeof(partial_result));
+      *partial_result = 1;
+      value -= *partial_result;
+
+      sub_task_pool = svn_task__create_pool(task);
+
+      partial_baton = apr_palloc(sub_task_pool, sizeof(partial_baton));    
+      *partial_baton = MAX(1, value - 1);
+      value -= *partial_baton;
+
+      SVN_ERR(svn_task__add_similar(&sub_task, task, sub_task_pool,
+                                    partial_result, partial_baton));
+    }
+
+  partial_result = apr_palloc(result_pool, sizeof(partial_result));
+  *partial_result = value;
+  *result = partial_result;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+sum_func(svn_task__t *task,
+         void *result,
+         void *output_baton,
+         svn_cancel_func_t cancel_func,
+         void *cancel_baton,
+         apr_pool_t *result_pool,
+         apr_pool_t *scratch_pool)
+{
+  apr_int64_t *result_p = result;
+  apr_int64_t *output_p = output_baton;
+  
+  if (result_p)
+    *output_p += *result_p;
+
+  if (cancel_func)
+    SVN_ERR(cancel_func(cancel_baton));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_counting(apr_pool_t *pool)
+{
+  apr_int64_t start = 1000000;
+  apr_int64_t result = 0;
+  SVN_ERR(svn_task__run(1, counter_func, &start, sum_func, &result,
+                        NULL, NULL, NULL, NULL, pool, pool));
+  SVN_TEST_ASSERT(result == start);
+
+  result = 0;
+  SVN_ERR(svn_task__run(4, counter_func, &start, sum_func, &result,
+                        NULL, NULL, NULL, NULL, pool, pool));
+  SVN_TEST_ASSERT(result == start);
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cancel_at_10k(void *baton)
+{
+  if (*(apr_int64_t*)baton == 10000)
+    return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_cancellation(apr_pool_t *pool)
+{
+  apr_int64_t start = 1000000;
+  apr_int64_t result = 0;
+  SVN_TEST_ASSERT_ERROR(svn_task__run(1, counter_func, &start, sum_func, &result,
+                                      NULL, NULL, cancel_at_10k, &result,
+                                      pool, pool),
+                        SVN_ERR_CANCELLED);
+  SVN_TEST_ASSERT(result == 10000);
+
+  result = 0;
+  SVN_TEST_ASSERT_ERROR(svn_task__run(8, counter_func, &start, sum_func, &result,
+                                      NULL, NULL, cancel_at_10k, &result,
+                                      pool, pool),
+                        SVN_ERR_CANCELLED);
+  SVN_TEST_ASSERT(result == 10000);
+  
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tree_walk_func(void **result,
+               svn_task__t *task,
+               void *thread_context,
+               void *process_baton,
+               svn_cancel_func_t cancel_func,
+               void *cancel_baton,
+               apr_pool_t *result_pool,
+               apr_pool_t *scratch_pool)
+{
+  const char *dir = (const char*)process_baton;
+
+  apr_hash_t *dirents = NULL;
+  apr_int64_t *partial_result;
+  apr_hash_index_t *iter;
+
+  svn_error_clear(svn_io_get_dirents3(&dirents, dir, TRUE, scratch_pool,
+                                      scratch_pool));
+
+  /* Walk all the children of this directory. */
+  if (dirents)
+    for (iter = apr_hash_first(scratch_pool, dirents); iter;
+         iter = apr_hash_next(iter))
+      {
+        svn_io_dirent2_t *dirent = apr_hash_this_val(iter);
+        if (dirent->kind == svn_node_dir)
+          {
+            svn_task__t *sub_task;
+            apr_pool_t *sub_task_pool = svn_task__create_pool(task);
+            const char *sub_dir = apr_hash_this_key(iter);
+
+            void *baton = apr_pstrcat(sub_task_pool, dir, "/", sub_dir, NULL);
+            SVN_ERR(svn_task__add_similar(&sub_task, task, sub_task_pool,
+                                          NULL, baton));
+          }
+      }
+
+  partial_result = apr_palloc(result_pool, sizeof(partial_result));
+  *partial_result = dirents ? apr_hash_count(dirents) : 0;
+  *result = partial_result;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_tree_walk(apr_pool_t *pool)
+{
+  apr_int64_t result = 0;
+  char *baton = apr_pstrdup(pool, "/media/stefan/Archive");
+  int i;
+  for (i = 0; i < 9; ++i)
+    {
+      SVN_ERR(svn_task__run(9, tree_walk_func, baton, sum_func, &result,
+                            NULL, NULL, NULL, NULL, pool, pool));
+    }
+  printf("%ld\n", result);
+  
+  return SVN_NO_ERROR;
+}
+
+/* An array of all test functions */
+
+static int max_threads = 1;
+
+static struct svn_test_descriptor_t test_funcs[] =
+  {
+    SVN_TEST_NULL,
+    SVN_TEST_PASS2(test_null_task,
+                   "null-task"),
+    SVN_TEST_PASS2(test_noop_task,
+                   "no-op task"),
+    SVN_TEST_PASS2(test_counting,
+                   "concurrent counting"),
+    SVN_TEST_PASS2(test_cancellation,
+                   "cancelling tasks"),
+    SVN_TEST_PASS2(test_tree_walk,
+                   "tree traversal"),
+    SVN_TEST_NULL
+  };
+
+SVN_TEST_MAIN

Property changes on: tests/libsvn_subr/task-test.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property

Index: include/private/svn_thread_cond.h
===================================================================
--- include/private/svn_thread_cond.h	(nonexistent)
+++ include/private/svn_thread_cond.h	(working copy)
@@ -0,0 +1,54 @@
+/**
+ * @copyright
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_thread_cond.h
+ * @brief Structures and functions for thread condition variables
+ */
+
+#ifndef SVN_THREAD_COND_H
+#define SVN_THREAD_COND_H
+
+#include "svn_mutex.h"
+
+/* A simple SVN-wrapper around the apr_thread_cond_* API */
+#if APR_HAS_THREADS
+#include <apr_thread_cond.h>
+typedef apr_thread_cond_t svn_thread_cond__t;
+#else
+typedef int svn_thread_cond__t;
+#endif
+
+svn_error_t *
+svn_thread_cond__create(svn_thread_cond__t **cond,
+                        apr_pool_t *result_pool);
+
+svn_error_t *
+svn_thread_cond__signal(svn_thread_cond__t *cond);
+
+svn_error_t *
+svn_thread_cond__broadcast(svn_thread_cond__t *cond);
+
+svn_error_t *
+svn_thread_cond__wait(svn_thread_cond__t *cond,
+                      svn_mutex__t *mutex);
+
+#endif

Property changes on: include/private/svn_thread_cond.h
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: include/private/svn_waitable_counter.h
===================================================================
--- include/private/svn_waitable_counter.h	(nonexistent)
+++ include/private/svn_waitable_counter.h	(working copy)
@@ -0,0 +1,57 @@
+/**
+ * @copyright
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_waitable_counter.h
+ * @brief Structures and functions for concurrent waitable counters
+ */
+
+#ifndef SVN_WAITABLE_COUNTER_H
+#define SVN_WAITABLE_COUNTER_H
+
+#include "svn_pools.h"
+
+/* Utility construct:  Clients can efficiently wait for the encapsulated
+ * counter to reach a certain value.  Currently, only increments have been
+ * implemented.  This whole structure can be opaque to the API users.
+ */
+typedef struct svn_waitable_counter_t svn_waitable_counter_t;
+
+/* Set *COUNTER_P to a new waitable_counter_t instance allocated in
+ * RESULT_POOL.  The initial counter value is 0. */
+svn_error_t *
+svn_waitable_counter__create(svn_waitable_counter_t **counter_p,
+                             apr_pool_t *result_pool);
+
+/* Increment the value in COUNTER by 1. */
+svn_error_t *
+svn_waitable_counter__increment(svn_waitable_counter_t *counter);
+
+/* Efficiently wait for COUNTER to assume VALUE. */
+svn_error_t *
+svn_waitable_counter__wait_for(svn_waitable_counter_t *counter,
+                               int value);
+
+/* Set the value in COUNTER to 0. */
+svn_error_t *
+svn_waitable_counter__reset(svn_waitable_counter_t *counter);
+
+#endif

Property changes on: include/private/svn_waitable_counter.h
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: libsvn_fs_x/batch_fsync.c
===================================================================
--- libsvn_fs_x/batch_fsync.c	(revision 1884516)
+++ libsvn_fs_x/batch_fsync.c	(working copy)
@@ -21,7 +21,6 @@
  */
 
 #include <apr_thread_pool.h>
-#include <apr_thread_cond.h>
 
 #include "batch_fsync.h"
 #include "svn_pools.h"
@@ -33,6 +32,8 @@
 #include "private/svn_dep_compat.h"
 #include "private/svn_mutex.h"
 #include "private/svn_subr_private.h"
+#include "private/svn_thread_cond.h"
+#include "private/svn_waitable_counter.h"
 
 /* Handy macro to check APR function results and turning them into
  * svn_error_t upon failure. */
@@ -43,140 +44,6 @@
       return svn_error_wrap_apr(status_, msg);  \
   }
 
-
-/* A simple SVN-wrapper around the apr_thread_cond_* API */
-#if APR_HAS_THREADS
-typedef apr_thread_cond_t svn_thread_cond__t;
-#else
-typedef int svn_thread_cond__t;
-#endif
-
-static svn_error_t *
-svn_thread_cond__create(svn_thread_cond__t **cond,
-                        apr_pool_t *result_pool)
-{
-#if APR_HAS_THREADS
-
-  WRAP_APR_ERR(apr_thread_cond_create(cond, result_pool),
-               _("Can't create condition variable"));
-
-#else
-
-  *cond = apr_pcalloc(result_pool, sizeof(**cond));
-
-#endif
-
-  return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-svn_thread_cond__broadcast(svn_thread_cond__t *cond)
-{
-#if APR_HAS_THREADS
-
-  WRAP_APR_ERR(apr_thread_cond_broadcast(cond),
-               _("Can't broadcast condition variable"));
-
-#endif
-
-  return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-svn_thread_cond__wait(svn_thread_cond__t *cond,
-                      svn_mutex__t *mutex)
-{
-#if APR_HAS_THREADS
-
-  WRAP_APR_ERR(apr_thread_cond_wait(cond, svn_mutex__get(mutex)),
-               _("Can't broadcast condition variable"));
-
-#endif
-
-  return SVN_NO_ERROR;
-}
-
-/* Utility construct:  Clients can efficiently wait for the encapsulated
- * counter to reach a certain value.  Currently, only increments have been
- * implemented.  This whole structure can be opaque to the API users.
- */
-typedef struct waitable_counter_t
-{
-  /* Current value, initialized to 0. */
-  int value;
-
-  /* Synchronization objects. */
-  svn_thread_cond__t *cond;
-  svn_mutex__t *mutex;
-} waitable_counter_t;
-
-/* Set *COUNTER_P to a new waitable_counter_t instance allocated in
- * RESULT_POOL.  The initial counter value is 0. */
-static svn_error_t *
-waitable_counter__create(waitable_counter_t **counter_p,
-                         apr_pool_t *result_pool)
-{
-  waitable_counter_t *counter = apr_pcalloc(result_pool, sizeof(*counter));
-  counter->value = 0;
-
-  SVN_ERR(svn_thread_cond__create(&counter->cond, result_pool));
-  SVN_ERR(svn_mutex__init(&counter->mutex, TRUE, result_pool));
-
-  *counter_p = counter;
-
-  return SVN_NO_ERROR;
-}
-
-/* Increment the value in COUNTER by 1. */
-static svn_error_t *
-waitable_counter__increment(waitable_counter_t *counter)
-{
-  SVN_ERR(svn_mutex__lock(counter->mutex));
-  counter->value++;
-
-  SVN_ERR(svn_thread_cond__broadcast(counter->cond));
-  SVN_ERR(svn_mutex__unlock(counter->mutex, SVN_NO_ERROR));
-
-  return SVN_NO_ERROR;
-}
-
-/* Efficiently wait for COUNTER to assume VALUE. */
-static svn_error_t *
-waitable_counter__wait_for(waitable_counter_t *counter,
-                           int value)
-{
-  svn_boolean_t done = FALSE;
-
-  /* This loop implicitly handles spurious wake-ups. */
-  do
-    {
-      SVN_ERR(svn_mutex__lock(counter->mutex));
-
-      if (counter->value == value)
-        done = TRUE;
-      else
-        SVN_ERR(svn_thread_cond__wait(counter->cond, counter->mutex));
-
-      SVN_ERR(svn_mutex__unlock(counter->mutex, SVN_NO_ERROR));
-    }
-  while (!done);
-
-  return SVN_NO_ERROR;
-}
-
-/* Set the value in COUNTER to 0. */
-static svn_error_t *
-waitable_counter__reset(waitable_counter_t *counter)
-{
-  SVN_ERR(svn_mutex__lock(counter->mutex));
-  counter->value = 0;
-  SVN_ERR(svn_mutex__unlock(counter->mutex, SVN_NO_ERROR));
-
-  SVN_ERR(svn_thread_cond__broadcast(counter->cond));
-
-  return SVN_NO_ERROR;
-}
-
 /* Entry type for the svn_fs_x__batch_fsync_t collection.  There is one
  * instance per file handle.
  */
@@ -193,7 +60,7 @@
   svn_error_t *result;
 
   /* Counter to increment when we completed the task. */
-  waitable_counter_t *counter;
+  svn_waitable_counter_t *counter;
 } to_sync_t;
 
 /* The actual collection object. */
@@ -203,7 +70,7 @@
   apr_hash_t *files;
 
   /* Counts the number of completed fsync tasks. */
-  waitable_counter_t *counter;
+  svn_waitable_counter_t *counter;
 
   /* Perform fsyncs only if this flag has been set. */
   svn_boolean_t flush_to_disk;
@@ -330,7 +197,7 @@
   result->files = svn_hash__make(result_pool);
   result->flush_to_disk = flush_to_disk;
 
-  SVN_ERR(waitable_counter__create(&result->counter, result_pool));
+  SVN_ERR(svn_waitable_counter__create(&result->counter, result_pool));
   apr_pool_cleanup_register(result_pool, result, fsync_batch_cleanup,
                             apr_pool_cleanup_null);
 
@@ -483,7 +350,7 @@
      OTOH, the main thread will probably deadlock anyway if we got
      an error here, thus there is no point in trying to tell the
      main thread what the problem was. */
-  svn_error_clear(waitable_counter__increment(to_sync->counter));
+  svn_error_clear(svn_waitable_counter__increment(to_sync->counter));
 
   return NULL;
 }
@@ -518,7 +385,7 @@
 
   /* Make sure the task completion counter is set to 0. */
   chain = svn_error_compose_create(chain,
-                                   waitable_counter__reset(batch->counter));
+                                   svn_waitable_counter__reset(batch->counter));
 
   /* Start the actual fsyncing process. */
   if (batch->flush_to_disk)
@@ -562,8 +429,8 @@
 
   /* Wait for all outstanding flush operations to complete. */
   chain = svn_error_compose_create(chain,
-                                   waitable_counter__wait_for(batch->counter,
-                                                              tasks));
+                                   svn_waitable_counter__wait_for(
+                                       batch->counter, tasks));
 
   /* Collect the results, close all files and release memory. */
   for (hi = apr_hash_first(scratch_pool, batch->files);
Index: libsvn_subr/thread_cond.c
===================================================================
--- libsvn_subr/thread_cond.c	(nonexistent)
+++ libsvn_subr/thread_cond.c	(working copy)
@@ -0,0 +1,91 @@
+/* thread_count.c --- implement SVN's wrapper around apr_thread_cond_t
+ *
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+#include "private/svn_thread_cond.h"
+
+/* Handy macro to check APR function results and turning them into
+ * svn_error_t upon failure. */
+#define WRAP_APR_ERR(x,msg)                     \
+  {                                             \
+    apr_status_t status_ = (x);                 \
+    if (status_)                                \
+      return svn_error_wrap_apr(status_, msg);  \
+  }
+
+
+svn_error_t *
+svn_thread_cond__create(svn_thread_cond__t **cond,
+                        apr_pool_t *result_pool)
+{
+#if APR_HAS_THREADS
+
+  WRAP_APR_ERR(apr_thread_cond_create(cond, result_pool),
+               "Can't create condition variable");
+
+#else
+
+  *cond = apr_pcalloc(result_pool, sizeof(**cond));
+
+#endif
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_thread_cond__signal(svn_thread_cond__t *cond)
+{
+#if APR_HAS_THREADS
+
+  WRAP_APR_ERR(apr_thread_cond_signal(cond),
+               "Can't signal condition variable");
+
+#endif
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_thread_cond__broadcast(svn_thread_cond__t *cond)
+{
+#if APR_HAS_THREADS
+
+  WRAP_APR_ERR(apr_thread_cond_broadcast(cond),
+               "Can't broadcast condition variable");
+
+#endif
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_thread_cond__wait(svn_thread_cond__t *cond,
+                      svn_mutex__t *mutex)
+{
+#if APR_HAS_THREADS
+
+  WRAP_APR_ERR(apr_thread_cond_wait(cond, svn_mutex__get(mutex)),
+               "Can't wait on condition variable");
+
+#endif
+
+  return SVN_NO_ERROR;
+}

Property changes on: libsvn_subr/thread_cond.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: libsvn_subr/waitable_counter.c
===================================================================
--- libsvn_subr/waitable_counter.c	(nonexistent)
+++ libsvn_subr/waitable_counter.c	(working copy)
@@ -0,0 +1,114 @@
+/* waitable_counter.c --- implement a concurrent waitable counter.
+ * 
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+#include "private/svn_waitable_counter.h"
+#include "private/svn_thread_cond.h"
+
+/* Handy macro to check APR function results and turning them into
+ * svn_error_t upon failure. */
+#define WRAP_APR_ERR(x,msg)                     \
+  {                                             \
+    apr_status_t status_ = (x);                 \
+    if (status_)                                \
+      return svn_error_wrap_apr(status_, msg);  \
+  }
+
+/* Utility construct:  Clients can efficiently wait for the encapsulated
+ * counter to reach a certain value.  Currently, only increments have been
+ * implemented.  This whole structure can be opaque to the API users.
+ */
+struct svn_waitable_counter_t
+{
+  /* Current value, initialized to 0. */
+  int value;
+
+  /* Synchronization objects. */
+  svn_thread_cond__t *cond;
+  svn_mutex__t *mutex;
+};
+
+/* Set *COUNTER_P to a new waitable_counter_t instance allocated in
+ * RESULT_POOL.  The initial counter value is 0. */
+svn_error_t *
+svn_waitable_counter__create(svn_waitable_counter_t **counter_p,
+                             apr_pool_t *result_pool)
+{
+  svn_waitable_counter_t *counter = apr_pcalloc(result_pool, sizeof(*counter));
+  counter->value = 0;
+
+  SVN_ERR(svn_thread_cond__create(&counter->cond, result_pool));
+  SVN_ERR(svn_mutex__init(&counter->mutex, TRUE, result_pool));
+
+  *counter_p = counter;
+
+  return SVN_NO_ERROR;
+}
+
+/* Increment the value in COUNTER by 1. */
+svn_error_t *
+svn_waitable_counter__increment(svn_waitable_counter_t *counter)
+{
+  SVN_ERR(svn_mutex__lock(counter->mutex));
+  counter->value++;
+
+  SVN_ERR(svn_thread_cond__broadcast(counter->cond));
+  SVN_ERR(svn_mutex__unlock(counter->mutex, SVN_NO_ERROR));
+
+  return SVN_NO_ERROR;
+}
+
+/* Efficiently wait for COUNTER to assume VALUE. */
+svn_error_t *
+svn_waitable_counter__wait_for(svn_waitable_counter_t *counter,
+                               int value)
+{
+  svn_boolean_t done = FALSE;
+
+  /* This loop implicitly handles spurious wake-ups. */
+  do
+    {
+      SVN_ERR(svn_mutex__lock(counter->mutex));
+
+      if (counter->value == value)
+        done = TRUE;
+      else
+        SVN_ERR(svn_thread_cond__wait(counter->cond, counter->mutex));
+
+      SVN_ERR(svn_mutex__unlock(counter->mutex, SVN_NO_ERROR));
+    }
+  while (!done);
+
+  return SVN_NO_ERROR;
+}
+
+/* Set the value in COUNTER to 0. */
+svn_error_t *
+svn_waitable_counter__reset(svn_waitable_counter_t *counter)
+{
+  SVN_ERR(svn_mutex__lock(counter->mutex));
+  counter->value = 0;
+  SVN_ERR(svn_mutex__unlock(counter->mutex, SVN_NO_ERROR));
+
+  SVN_ERR(svn_thread_cond__broadcast(counter->cond));
+
+  return SVN_NO_ERROR;
+}

Property changes on: libsvn_subr/waitable_counter.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property

Index: status.c
===================================================================
--- status.c	(revision 1884516)
+++ status.c	(working copy)
@@ -51,7 +51,10 @@
 #include "private/svn_wc_private.h"
 #include "private/svn_fspath.h"
 #include "private/svn_editor.h"
+#include "private/svn_task.h"
 
+#include "translate.h"
+
 
 /* The file internal variant of svn_wc_status3_t, with slightly more
    data.
@@ -75,6 +78,28 @@
 } svn_wc__internal_status_t;
 
 
+/* Forward declare */
+struct walk_status_baton;
+
+typedef svn_error_t *
+(*dir_status_func_t)(const struct walk_status_baton *wb,
+                     const char *local_abspath,
+                     svn_boolean_t skip_this_dir,
+                     const char *parent_repos_root_url,
+                     const char *parent_repos_relpath,
+                     const char *parent_repos_uuid,
+                     const struct svn_wc__db_info_t *dir_info,
+                     const svn_io_dirent2_t *dirent,
+                     const apr_array_header_t *ignore_patterns,
+                     svn_depth_t depth,
+                     svn_boolean_t get_all,
+                     svn_boolean_t no_ignore,
+                     svn_wc_status_func4_t status_func,
+                     void *status_baton,
+                     svn_cancel_func_t cancel_func,
+                     void *cancel_baton,
+                     apr_pool_t *scratch_pool);
+
 /*** Baton used for walking the local status */
 struct walk_status_baton
 {
@@ -100,6 +125,9 @@
 
   /* Repository locks, if set. */
   apr_hash_t *repos_locks;
+
+  dir_status_func_t dir_status_func;
+  void *dir_status_baton;
 };
 
 /*** Editor batons ***/
@@ -316,6 +344,116 @@
                 apr_pool_t *result_pool,
                 apr_pool_t *scratch_pool);
 
+static svn_error_t *
+compare_and_verify(svn_boolean_t *modified_p,
+                   svn_wc__db_t *db,
+                   const char *versioned_file_abspath,
+                   svn_filesize_t versioned_file_size,
+                   svn_stream_t *pristine_stream,
+                   svn_filesize_t pristine_size,
+                   svn_boolean_t has_props,
+                   svn_boolean_t props_mod,
+                   svn_boolean_t exact_comparison,
+                   apr_pool_t *scratch_pool)
+{
+  svn_boolean_t same;
+  svn_subst_eol_style_t eol_style;
+  const char *eol_str;
+  apr_hash_t *keywords;
+  svn_boolean_t special = FALSE;
+  svn_boolean_t need_translation;
+  svn_stream_t *v_stream; /* versioned_file */
+
+  *modified_p = FALSE;
+
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_file_abspath));
+
+  if (props_mod)
+    has_props = TRUE; /* Maybe it didn't have properties; but it has now */
+
+  if (has_props)
+    {
+      SVN_ERR(svn_wc__get_translate_info(&eol_style, &eol_str,
+                                         &keywords,
+                                         &special,
+                                         db, versioned_file_abspath, NULL,
+                                         !exact_comparison,
+                                         scratch_pool, scratch_pool));
+
+      need_translation = svn_subst_translation_required(eol_style, eol_str,
+                                                        keywords, special,
+                                                        TRUE);
+    }
+  else
+    need_translation = FALSE;
+
+
+  if (! need_translation
+      && (versioned_file_size != pristine_size))
+    {
+      *modified_p = TRUE;
+
+      /* ### Why did we open the pristine? */
+      return svn_error_trace(svn_stream_close(pristine_stream));
+    }
+
+  /* ### Other checks possible? */
+
+  /* Reading files is necessary. */
+  if (special && need_translation)
+    {
+      SVN_ERR(svn_subst_read_specialfile(&v_stream, versioned_file_abspath,
+                                          scratch_pool, scratch_pool));
+    }
+  else
+    {
+      /* We don't use APR-level buffering because the comparison function
+       * will do its own buffering. */
+      apr_file_t *file;
+      SVN_ERR(svn_io_file_open(&file, versioned_file_abspath, APR_READ,
+                               APR_OS_DEFAULT, scratch_pool));
+      v_stream = svn_stream_from_aprfile2(file, FALSE, scratch_pool);
+
+      if (need_translation)
+        {
+          if (!exact_comparison)
+            {
+              if (eol_style == svn_subst_eol_style_native)
+                eol_str = SVN_SUBST_NATIVE_EOL_STR;
+              else if (eol_style != svn_subst_eol_style_fixed
+                       && eol_style != svn_subst_eol_style_none)
+                return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL,
+                                        svn_stream_close(v_stream), NULL);
+
+              /* Wrap file stream to detranslate into normal form,
+               * "repairing" the EOL style if it is inconsistent. */
+              v_stream = svn_subst_stream_translated(v_stream,
+                                                     eol_str,
+                                                     TRUE /* repair */,
+                                                     keywords,
+                                                     FALSE /* expand */,
+                                                     scratch_pool);
+            }
+          else
+            {
+              /* Wrap base stream to translate into working copy form, and
+               * arrange to throw an error if its EOL style is inconsistent. */
+              pristine_stream = svn_subst_stream_translated(pristine_stream,
+                                                            eol_str, FALSE,
+                                                            keywords, TRUE,
+                                                            scratch_pool);
+            }
+        }
+    }
+
+  SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream,
+                                    scratch_pool));
+
+  *modified_p = (! same);
+
+  return SVN_NO_ERROR;
+}
+
 /* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in
    RESULT_POOL and use SCRATCH_POOL for temporary allocations.
 
@@ -470,23 +608,42 @@
             text_modified_p = FALSE;
           else
             {
+              svn_stream_t *pristine_stream;
+              svn_filesize_t pristine_size;
+              const svn_checksum_t *checksum;
               svn_error_t *err;
-              err = svn_wc__internal_file_modified_p(&text_modified_p,
-                                                     db, local_abspath,
-                                                     FALSE, scratch_pool);
 
-              if (err)
+              SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                                           NULL, NULL, NULL, &checksum, NULL, NULL, NULL,
+                                           NULL, NULL, NULL,
+                                           NULL, NULL,
+                                           NULL, NULL, NULL, NULL, NULL,
+                                           NULL, NULL, NULL,
+                                           db, local_abspath,
+                                           scratch_pool, scratch_pool));
+              
+              SVN_ERR(svn_wc__db_pristine_read(&pristine_stream, &pristine_size,
+                                               db, local_abspath, checksum,
+                                               scratch_pool, scratch_pool));
+
+              /* Check all bytes, and verify checksum if requested. */
+              err = compare_and_verify(&text_modified_p, db,
+                                       local_abspath, dirent->filesize,
+                                       pristine_stream, pristine_size,
+                                       info->had_props, info->props_mod,
+                                       FALSE, scratch_pool);
+
+              /* An access denied is very common on Windows when another
+                  application has the file open.  Previously we ignored
+                  this error in svn_wc__text_modified_internal_p, where it
+                  should have really errored. */
+              if (err && APR_STATUS_IS_EACCES(err->apr_err))
                 {
-                  if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED)
-                    return svn_error_trace(err);
-
-                  /* An access denied is very common on Windows when another
-                     application has the file open.  Previously we ignored
-                     this error in svn_wc__text_modified_internal_p, where it
-                     should have really errored. */
                   svn_error_clear(err);
                   text_modified_p = TRUE;
                 }
+              else
+                SVN_ERR(err);
             }
         }
 #ifdef HAVE_SYMLINK
@@ -1073,6 +1230,14 @@
  * allocations are made in SCRATCH_POOL.
  *
  * The remaining parameters correspond to get_dir_status(). */
+
+struct status_task_baton
+{
+  svn_task__t *task;
+  apr_pool_t *result_pool;
+  apr_array_header_t *partial_results;
+};
+
 static svn_error_t *
 one_child_status(const struct walk_status_baton *wb,
                  const char *local_abspath,
@@ -1123,15 +1288,15 @@
       if (depth == svn_depth_infinity
           && info->has_descendants /* is dir, or was dir and tc descendants */)
         {
-          SVN_ERR(get_dir_status(wb, local_abspath, TRUE,
-                                 dir_repos_root_url, dir_repos_relpath,
-                                 dir_repos_uuid, info,
-                                 dirent, ignore_patterns,
-                                 svn_depth_infinity, get_all,
-                                 no_ignore,
-                                 status_func, status_baton,
-                                 cancel_func, cancel_baton,
-                                 scratch_pool));
+          SVN_ERR(wb->dir_status_func(wb, local_abspath, TRUE,
+                                      dir_repos_root_url, dir_repos_relpath,
+                                      dir_repos_uuid, info,
+                                      dirent, ignore_patterns,
+                                      svn_depth_infinity, get_all,
+                                      no_ignore,
+                                      status_func, status_baton,
+                                      cancel_func, cancel_baton,
+                                      scratch_pool));
         }
 
       return SVN_NO_ERROR;
@@ -1278,6 +1443,11 @@
   /* Create a hash containing all children.  The source hashes
      don't all map the same types, but only the keys of the result
      hash are subsequently used. */
+  if (svn_hash_gets(dirents, ".svn") == NULL)
+    {
+      SVN_ERR(svn_wc__db_plain_sub_dir(wb->db, local_abspath, iterpool));
+    }
+
   SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
                                         wb->db, local_abspath,
                                         !wb->check_working_copy,
@@ -2457,6 +2627,330 @@
 
 
 
+
+/* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and
+  *   for all its child nodes (according to DEPTH) through STATUS_FUNC /
+  *   STATUS_BATON.
+  * 
+  *   If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported.
+  *   All subdirs reached by recursion will be reported regardless of this
+  *   parameter's value.
+  * 
+  *   PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's
+  *   URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid
+  *   retrieving them again. Otherwise they must be NULL.
+  * 
+  *   DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving
+  *   it again. Otherwise it must be NULL.
+  * 
+  *   DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported,
+  *   so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL.
+  * 
+  *   Other arguments are the same as those passed to
+  *   svn_wc_get_status_editor5().  */
+ 
+typedef struct dir_status_process_baton_t
+{
+  const char *local_abspath;
+  svn_boolean_t skip_this_dir;
+  const char *parent_repos_root_url;
+  const char *parent_repos_relpath;
+  const char *parent_repos_uuid;
+  const struct svn_wc__db_info_t *dir_info;
+  const svn_io_dirent2_t *dirent;
+  const apr_array_header_t *ignore_patterns;
+  svn_depth_t depth;
+  svn_boolean_t get_all;
+  svn_boolean_t no_ignore;
+} dir_status_process_baton_t;
+
+typedef struct dir_status_result_t
+{
+  svn_wc_status3_t *status;
+  const char *local_abspath;
+} dir_status_result_t;
+
+static svn_error_t *
+add_status_result(void *baton,
+                  const char *local_abspath,
+                  const svn_wc_status3_t *status,
+                  apr_pool_t *scratch_pool)
+{
+  struct status_task_baton *b = baton;
+  apr_pool_t *result_pool = b->result_pool;
+  apr_array_header_t *results = b->partial_results;
+
+  dir_status_result_t *result = apr_pcalloc(result_pool, sizeof(*result));
+  result->status = svn_wc_dup_status3(status, result_pool);
+  result->local_abspath = apr_pstrdup(result_pool, local_abspath);
+
+  if (results == NULL)
+    {
+      results = apr_array_make(result_pool, 8, sizeof(dir_status_result_t*));
+      b->partial_results = results;
+    }
+
+  APR_ARRAY_PUSH(results, dir_status_result_t *) = result;
+
+  return SVN_NO_ERROR;
+}
+
+/* Structure returned by svn_wc__db_read_children_info.  Only has the
+ *   fields needed by status. */
+static struct svn_wc__db_info_t *
+db_info_dup(const struct svn_wc__db_info_t *info,
+            apr_pool_t *result_pool)
+{
+  struct svn_wc__db_moved_to_info_t **moves;
+  struct svn_wc__db_info_t *result = apr_pmemdup(result_pool, info,
+                                                 sizeof(*result));
+
+  result->repos_relpath  = apr_pstrdup(result_pool, info->repos_relpath);
+  result->repos_root_url = apr_pstrdup(result_pool, info->repos_root_url);
+  result->repos_uuid     = apr_pstrdup(result_pool, info->repos_uuid);
+
+  if (info->changelist)
+    result->changelist     = apr_pstrdup(result_pool, info->changelist);
+  if (info->changed_author)
+    result->changed_author = apr_pstrdup(result_pool, info->changed_author);
+
+  if (info->lock)
+    {
+      result->lock = apr_pmemdup(result_pool, info->lock,
+                                 sizeof(*result->lock));
+      result->lock->token = apr_pstrdup(result_pool, info->lock->token);
+      if (info->lock->owner)
+        result->lock->owner = apr_pstrdup(result_pool, info->lock->owner);
+      if (info->lock->comment)
+        result->lock->comment = apr_pstrdup(result_pool, info->lock->comment);
+    }
+  
+  for (moves = &result->moved_to; *moves; moves = &(*moves)->next)
+    {
+      *moves = apr_pmemdup(result_pool, *moves, sizeof(**moves));
+      (*moves)->moved_to_abspath =
+          apr_pstrdup(result_pool, (*moves)->moved_to_abspath);
+      (*moves)->shadow_op_root_abspath =
+          apr_pstrdup(result_pool, (*moves)->shadow_op_root_abspath);
+    }
+  
+  return result;
+}
+
+static svn_error_t *
+create_dir_status_task(const struct walk_status_baton *wb,
+                       const char *local_abspath,
+                       svn_boolean_t skip_this_dir,
+                       const char *parent_repos_root_url,
+                       const char *parent_repos_relpath,
+                       const char *parent_repos_uuid,
+                       const struct svn_wc__db_info_t *dir_info,
+                       const svn_io_dirent2_t *dirent,
+                       const apr_array_header_t *ignore_patterns,
+                       svn_depth_t depth,
+                       svn_boolean_t get_all,
+                       svn_boolean_t no_ignore,
+                       svn_wc_status_func4_t status_func,
+                       void *status_baton,
+                       svn_cancel_func_t cancel_func,
+                       void *cancel_baton,
+                       apr_pool_t *scratch_pool)
+{
+  struct status_task_baton *baton = wb->dir_status_baton;
+  svn_task__t *sub_task;
+
+  apr_pool_t *process_pool = svn_task__create_pool(baton->task);
+  dir_status_process_baton_t *sub_baton =
+      apr_pcalloc(process_pool, sizeof(*sub_baton));
+
+  sub_baton->local_abspath =
+      apr_pstrdup(process_pool, local_abspath);
+  sub_baton->parent_repos_root_url =
+      apr_pstrdup(process_pool, parent_repos_root_url);
+  sub_baton->parent_repos_relpath =
+      apr_pstrdup(process_pool, parent_repos_relpath);
+  sub_baton->parent_repos_uuid =
+      apr_pstrdup(process_pool, parent_repos_uuid);
+  sub_baton->dirent =
+      svn_io_dirent2_dup(dirent, process_pool);
+  sub_baton->dir_info =
+      db_info_dup(dir_info, process_pool);
+  sub_baton->skip_this_dir = TRUE;
+
+  sub_baton->ignore_patterns = ignore_patterns;
+  sub_baton->depth = depth;
+  sub_baton->get_all = get_all;
+  sub_baton->no_ignore = no_ignore;
+
+  SVN_ERR(svn_task__add_similar(&sub_task, baton->task, process_pool,
+                                baton->partial_results &&
+                                baton->partial_results->nelts
+                                  ? baton->partial_results
+                                  : NULL,
+                                sub_baton));
+  baton->partial_results = NULL;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_dir_status_process(void **result,
+                       svn_task__t *task,
+                       void *thread_context,
+                       void *process_baton,
+                       svn_cancel_func_t cancel_func,
+                       void *cancel_baton,
+                       apr_pool_t *result_pool,
+                       apr_pool_t *scratch_pool)
+{
+  dir_status_process_baton_t *baton = process_baton;
+  struct walk_status_baton *wb = thread_context;
+
+  struct status_task_baton status_baton;
+  status_baton.task = task;
+  status_baton.result_pool = result_pool;
+  status_baton.partial_results = apr_array_make(result_pool, 0,
+                                                sizeof(dir_status_result_t*));
+
+  wb->dir_status_func = create_dir_status_task;
+  wb->dir_status_baton = &status_baton;
+
+  SVN_ERR(get_dir_status(wb, baton->local_abspath,
+                         baton->skip_this_dir,
+                         baton->parent_repos_root_url,
+                         baton->parent_repos_relpath,
+                         baton->parent_repos_uuid,
+                         baton->dir_info,
+                         baton->dirent,
+                         baton->ignore_patterns,
+                         baton->depth,
+                         baton->get_all,
+                         baton->no_ignore,
+                         add_status_result,
+                         &status_baton,
+                         cancel_func,
+                         cancel_baton,
+                         scratch_pool));
+
+  *result = status_baton.partial_results && status_baton.partial_results->nelts
+          ? status_baton.partial_results
+          : NULL;
+  return SVN_NO_ERROR;
+}
+
+typedef struct dir_status_output_baton_t
+{
+  svn_wc_status_func4_t status_func;
+  void *status_baton;
+} dir_status_output_baton_t;
+
+static svn_error_t *
+get_dir_status_output(svn_task__t *task,
+                      void *result,
+                      void *output_baton,
+                      svn_cancel_func_t cancel_func,
+                      void *cancel_baton,
+                      apr_pool_t *result_pool,
+                      apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *to_send = result;
+  dir_status_output_baton_t *baton = output_baton;
+  int i;
+
+  for (i = 0; i < to_send->nelts; ++i)
+    {
+      dir_status_result_t *status =
+          APR_ARRAY_IDX(to_send, i, dir_status_result_t *);
+      if (cancel_func)
+        SVN_ERR(cancel_func(cancel_baton));
+
+      if (baton->status_func)
+        SVN_ERR(baton->status_func(baton->status_baton,
+                                   status->local_abspath,
+                                   status->status,
+                                   scratch_pool));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_dir_context_creator(void **thread_context,
+                        void *baton,
+                        apr_pool_t *result_pool,
+                        apr_pool_t *scratch_pool)
+{
+  const struct walk_status_baton *wb = baton;
+
+  struct walk_status_baton *result = apr_pcalloc(result_pool,
+                                                 sizeof(*result));
+  SVN_ERR(svn_wc__db_open(&result->db, NULL, FALSE, FALSE,
+                          result_pool, scratch_pool));
+  result->externals = wb->externals;
+  result->target_abspath = wb->target_abspath;
+  result->ignore_text_mods = wb->ignore_text_mods;
+  result->check_working_copy = wb->check_working_copy;
+  result->repos_root = NULL;
+  result->repos_locks = NULL;
+
+  *thread_context = result;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_dir_status_parallel(struct walk_status_baton *wb,
+                        const char *local_abspath,
+                        svn_boolean_t skip_this_dir,
+                        const char *parent_repos_root_url,
+                        const char *parent_repos_relpath,
+                        const char *parent_repos_uuid,
+                        const struct svn_wc__db_info_t *dir_info,
+                        const svn_io_dirent2_t *dirent,
+                        const apr_array_header_t *ignore_patterns,
+                        svn_depth_t depth,
+                        svn_boolean_t get_all,
+                        svn_boolean_t no_ignore,
+                        svn_wc_status_func4_t status_func,
+                        void *status_baton,
+                        svn_cancel_func_t cancel_func,
+                        void *cancel_baton,
+                        apr_pool_t *scratch_pool)
+{
+  dir_status_process_baton_t process_baton;
+  dir_status_output_baton_t output_baton;
+
+  process_baton.local_abspath = local_abspath;
+  process_baton.skip_this_dir = skip_this_dir;
+  process_baton.parent_repos_root_url = parent_repos_root_url;
+  process_baton.parent_repos_relpath = parent_repos_relpath;
+  process_baton.parent_repos_uuid = parent_repos_uuid;
+  process_baton.dir_info = dir_info;
+  process_baton.dirent = dirent;
+  process_baton.ignore_patterns = ignore_patterns;
+  process_baton.depth = depth;
+  process_baton.get_all = get_all;
+  process_baton.no_ignore = no_ignore;
+
+  output_baton.status_func = status_func;
+  output_baton.status_baton = status_baton;
+
+  SVN_ERR(svn_task__run(24,
+                        get_dir_status_process, &process_baton,
+                        get_dir_status_output, &output_baton,
+                        get_dir_context_creator, wb,
+                        cancel_func, cancel_baton,
+                        scratch_pool, scratch_pool));
+/*
+  SVN_ERR(svn_task__run(1,
+                        get_dir_status_process, &process_baton,
+                        get_dir_status_output, &output_baton,
+                        get_dir_context_creator, wb,
+                        cancel_func, cancel_baton,
+                        scratch_pool, scratch_pool));
+*/
+  return SVN_NO_ERROR;
+}
+
 /*** Public API ***/
 
 svn_error_t *
@@ -2644,6 +3138,8 @@
   wb.check_working_copy = TRUE;
   wb.repos_root = NULL;
   wb.repos_locks = NULL;
+  wb.dir_status_func = get_dir_status;
+  wb.dir_status_baton = NULL;
 
   /* Use the caller-provided ignore patterns if provided; the build-time
      configured defaults otherwise. */
@@ -2690,19 +3186,19 @@
       && info->status != svn_wc__db_status_excluded
       && info->status != svn_wc__db_status_server_excluded)
     {
-      SVN_ERR(get_dir_status(&wb,
-                             local_abspath,
-                             FALSE /* skip_root */,
-                             NULL, NULL, NULL,
-                             info,
-                             dirent,
-                             ignore_patterns,
-                             depth,
-                             get_all,
-                             no_ignore,
-                             status_func, status_baton,
-                             cancel_func, cancel_baton,
-                             scratch_pool));
+      SVN_ERR(get_dir_status_parallel(&wb,
+                                      local_abspath,
+                                      FALSE /* skip_root */,
+                                      NULL, NULL, NULL,
+                                      info,
+                                      dirent,
+                                      ignore_patterns,
+                                      depth,
+                                      get_all,
+                                      no_ignore,
+                                      status_func, status_baton,
+                                      cancel_func, cancel_baton,
+                                      scratch_pool));
     }
   else
     {
Index: wc_db.c
===================================================================
--- wc_db.c	(revision 1884516)
+++ wc_db.c	(working copy)
@@ -9600,6 +9600,24 @@
   return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_wc__db_plain_sub_dir(svn_wc__db_t *db,
+                         const char *dir_abspath,
+                         apr_pool_t *scratch_pool)
+{
+  const char *parent_dir = svn_dirent_dirname(dir_abspath, scratch_pool);
+  svn_wc__db_wcroot_t *wcroot = svn_hash_gets(db->dir_data, parent_dir);
+
+  if (wcroot)
+    {
+      svn_hash_sets(db->dir_data,
+                    apr_pstrdup(db->state_pool, dir_abspath),
+                    wcroot);
+    }
+
+  return SVN_NO_ERROR;
+}
+
 /* Implementation of svn_wc__db_read_single_info.
 
    ### This function is very similar to a lot of code inside
Index: wc_db.h
===================================================================
--- wc_db.h	(revision 1884516)
+++ wc_db.h	(working copy)
@@ -2047,6 +2047,11 @@
                               apr_pool_t *result_pool,
                               apr_pool_t *scratch_pool);
 
+svn_error_t *
+svn_wc__db_plain_sub_dir(svn_wc__db_t *db,
+                         const char *dir_abspath,
+                         apr_pool_t *scratch_pool);
+
 /* Like svn_wc__db_read_children_info, but only gets an info node for the root
    element.
 
Index: wc_db_private.h
===================================================================
--- wc_db_private.h	(revision 1884516)
+++ wc_db_private.h	(working copy)
@@ -148,6 +148,15 @@
                                       apr_pool_t *result_pool,
                                       apr_pool_t *scratch_pool);
 
+svn_error_t *
+svn_wc__db_wcroot_parse_local_abspath2(svn_wc__db_wcroot_t **wcroot,
+                                       const char **local_relpath,
+                                       svn_wc__db_t *db,
+                                       const char *local_abspath,
+                                       svn_node_kind_t kind,
+                                       apr_pool_t *result_pool,
+                                       apr_pool_t *scratch_pool);
+
 /* Return an error if the work queue in SDB is non-empty. */
 svn_error_t *
 svn_wc__db_verify_no_work(svn_sqlite__db_t *sdb);
Index: wc_db_wcroot.c
===================================================================
--- wc_db_wcroot.c	(revision 1884516)
+++ wc_db_wcroot.c	(working copy)
@@ -507,16 +507,16 @@
 
 
 svn_error_t *
-svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot,
-                                      const char **local_relpath,
-                                      svn_wc__db_t *db,
-                                      const char *local_abspath,
-                                      apr_pool_t *result_pool,
-                                      apr_pool_t *scratch_pool)
+svn_wc__db_wcroot_parse_local_dir_abspath(svn_wc__db_wcroot_t **wcroot,
+                                          const char **local_relpath,
+                                          svn_wc__db_t *db,
+                                          const char *local_abspath,
+                                          svn_node_kind_t kind,
+                                          apr_pool_t *result_pool,
+                                          apr_pool_t *scratch_pool)
 {
   const char *local_dir_abspath;
   const char *original_abspath = local_abspath;
-  svn_node_kind_t kind;
   const char *build_relpath;
   svn_wc__db_wcroot_t *probe_wcroot;
   svn_wc__db_wcroot_t *found_wcroot = NULL;
@@ -550,11 +550,13 @@
       return SVN_NO_ERROR;
     }
 
-  /* ### at some point in the future, we may need to find a way to get
-     ### rid of this stat() call. it is going to happen for EVERY call
-     ### into wc_db which references a file. calls for directories could
-     ### get an early-exit in the hash lookup just above.  */
-  SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool));
+  /* The caller should often be able to tell us what the node type is.
+     When it can't, we go and check ourselves. */
+  if (kind == svn_node_unknown)
+    {
+      SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool));
+    }
+  
   if (kind != svn_node_dir)
     {
       /* If the node specified by the path is NOT present, then it cannot
@@ -982,8 +984,499 @@
   return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_wc__db_wcroot_parse_local_abspath2(svn_wc__db_wcroot_t **wcroot,
+                                       const char **local_relpath,
+                                       svn_wc__db_t *db,
+                                       const char *local_abspath,
+                                       svn_node_kind_t kind,
+                                       apr_pool_t *result_pool,
+                                       apr_pool_t *scratch_pool)
+{
+  const char *local_dir_abspath;
+  const char *original_abspath = local_abspath;
+  const char *build_relpath;
+  svn_wc__db_wcroot_t *probe_wcroot;
+  svn_wc__db_wcroot_t *found_wcroot = NULL;
+  const char *scan_abspath;
+  svn_sqlite__db_t *sdb = NULL;
+  svn_boolean_t moved_upwards = FALSE;
+  svn_boolean_t always_check = FALSE;
+  int wc_format = 0;
+  const char *adm_relpath;
+  /* Non-NULL if WCROOT is found through a symlink: */
+  const char *symlink_wcroot_abspath = NULL;
+  apr_pool_t *iterpool;
 
+  /* ### we need more logic for finding the database (if it is located
+     ### outside of the wcroot) and then managing all of that within DB.
+     ### for now: play quick & dirty. */
+
+  probe_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+  if (probe_wcroot != NULL)
+    {
+      *wcroot = probe_wcroot;
+
+      /* We got lucky. Just return the thing BEFORE performing any I/O.  */
+      /* ### validate SMODE against how we opened wcroot->sdb? and against
+         ### DB->mode? (will we record per-dir mode?)  */
+
+      /* ### for most callers, we could pass NULL for result_pool.  */
+      *local_relpath = compute_relpath(probe_wcroot, local_abspath,
+                                       result_pool);
+
+      return SVN_NO_ERROR;
+    }
+
+  /* The caller should often be able to tell us what the node type is.
+     When it can't, we go and check ourselves. */
+  if (kind == svn_node_unknown)
+    {
+      SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool));
+    }
+  
+  if (kind != svn_node_dir)
+    {
+      /* If the node specified by the path is NOT present, then it cannot
+         possibly be a directory containing ".svn/wc.db".
+
+         If it is a file, then it cannot contain ".svn/wc.db".
+
+         For both of these cases, strip the basename off of the path and
+         move up one level. Keep record of what we strip, though, since
+         we'll need it later to construct local_relpath.  */
+      svn_dirent_split(&local_dir_abspath, &build_relpath, local_abspath,
+                       scratch_pool);
+
+      /* Is this directory in our hash?  */
+      probe_wcroot = svn_hash_gets(db->dir_data, local_dir_abspath);
+      if (probe_wcroot != NULL)
+        {
+          const char *dir_relpath;
+
+          *wcroot = probe_wcroot;
+
+          /* Stashed directory's local_relpath + basename. */
+          dir_relpath = compute_relpath(probe_wcroot, local_dir_abspath,
+                                        NULL);
+          *local_relpath = svn_relpath_join(dir_relpath,
+                                            build_relpath,
+                                            result_pool);
+          return SVN_NO_ERROR;
+        }
+
+      /* If the requested path is not on the disk, then we don't know how
+         many ancestors need to be scanned until we start hitting content
+         on the disk. Set ALWAYS_CHECK to keep looking for .svn/entries
+         rather than bailing out after the first check.  */
+      if (kind == svn_node_none)
+        always_check = TRUE;
+
+      /* Start the scanning at LOCAL_DIR_ABSPATH.  */
+      local_abspath = local_dir_abspath;
+    }
+  else
+    {
+      /* Start the local_relpath empty. If *this* directory contains the
+         wc.db, then relpath will be the empty string.  */
+      build_relpath = "";
+
+      /* Remember the dir containing LOCAL_ABSPATH (they're the same).  */
+      local_dir_abspath = local_abspath;
+    }
+
+  /* LOCAL_ABSPATH refers to a directory at this point. At this point,
+     we've determined that an associated WCROOT is NOT in the DB's hash
+     table for this directory. Let's find an existing one in the ancestors,
+     or create one when we find the actual wcroot.  */
+
+  /* Assume that LOCAL_ABSPATH is a directory, and look for the SQLite
+     database in the right place. If we find it... great! If not, then
+     peel off some components, and try again. */
+
+  iterpool = svn_pool_create(scratch_pool);
+  adm_relpath = svn_wc_get_adm_dir(scratch_pool);
+  while (TRUE)
+    {
+      svn_error_t *err;
+      svn_node_kind_t adm_subdir_kind;
+
+      const char *adm_subdir;
+
+      svn_pool_clear(iterpool);
+
+      adm_subdir = svn_dirent_join(local_abspath, adm_relpath, iterpool);
+
+      SVN_ERR(svn_io_check_path(adm_subdir, &adm_subdir_kind, iterpool));
+
+      if (adm_subdir_kind == svn_node_dir)
+        {
+          /* We always open the database in read/write mode.  If the database
+             isn't writable in the filesystem, SQLite will internally open
+             it as read-only, and we'll get an error if we try to do a write
+             operation.
+
+             We could decide what to do on a per-operation basis, but since
+             we're caching database handles, it make sense to be as permissive
+             as the filesystem allows. */
+          err = svn_wc__db_util_open_db(&sdb, local_abspath, SDB_FILE,
+                                        svn_sqlite__mode_readwrite,
+                                        db->exclusive, db->timeout, NULL,
+                                        db->state_pool, scratch_pool);
+          if (err == NULL)
+            {
+#ifdef SVN_DEBUG
+              /* Install self-verification trigger statements. */
+              err = svn_sqlite__exec_statements(sdb,
+                                                STMT_VERIFICATION_TRIGGERS);
+              if (err && err->apr_err == SVN_ERR_SQLITE_ERROR)
+                {
+                  /* Verification triggers can fail to install on old 1.7-dev
+                   * formats which didn't have a NODES table yet. Ignore sqlite
+                   * errors so such working copies can be upgraded. */
+                  svn_error_clear(err);
+                }
+              else
+                SVN_ERR(err);
+#endif
+              break;
+            }
+          if (err->apr_err != SVN_ERR_SQLITE_ERROR
+              && !APR_STATUS_IS_ENOENT(err->apr_err))
+            return svn_error_trace(err);
+          svn_error_clear(err);
+
+          /* If we have not moved upwards, then check for a wc-1 working copy.
+             Since wc-1 has a .svn in every directory, and we didn't find one
+             in the original directory, then we aren't looking at a wc-1.
+
+             If the original path is not present, then we have to check on every
+             iteration. The content may be the immediate parent, or possibly
+             five ancetors higher. We don't test for directory presence (just
+             for the presence of subdirs/files), so we don't know when we can
+             stop checking ... so just check always.  */
+          if (!moved_upwards || always_check)
+            {
+              SVN_ERR(get_old_version(&wc_format, local_abspath,
+                                      iterpool));
+              if (wc_format != 0)
+                break;
+            }
+        }
+
+      /* We couldn't open the SDB within the specified directory, so
+         move up one more directory. */
+      if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+        {
+          /* Hit the root without finding a wcroot. */
+
+          /* The wcroot could be a symlink to a directory.
+           * (Issue #2557, #3987). If so, try again, this time scanning
+           * for a db within the directory the symlink points to,
+           * rather than within the symlink's parent directory. */
+          if (kind == svn_node_symlink)
+            {
+              svn_node_kind_t resolved_kind;
+
+              local_abspath = original_abspath;
+
+              SVN_ERR(svn_io_check_resolved_path(local_abspath,
+                                                 &resolved_kind,
+                                                 iterpool));
+              if (resolved_kind == svn_node_dir)
+                {
+                  /* Is this directory recorded in our hash?  */
+                  found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+                  if (found_wcroot)
+                    break;
+
+                  symlink_wcroot_abspath = local_abspath;
+                  SVN_ERR(read_link_target(&local_abspath, local_abspath,
+                                           scratch_pool));
+try_symlink_as_dir:
+                  kind = svn_node_dir;
+                  moved_upwards = FALSE;
+                  local_dir_abspath = local_abspath;
+                  build_relpath = "";
+
+                  continue;
+                }
+            }
+
+          return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+                                   _("'%s' is not a working copy"),
+                                   svn_dirent_local_style(original_abspath,
+                                                          scratch_pool));
+        }
+
+      local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+      moved_upwards = TRUE;
+      symlink_wcroot_abspath = NULL;
+
+      /* Is the parent directory recorded in our hash?  */
+      found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+      if (found_wcroot != NULL)
+        break;
+    }
+
+  if (found_wcroot != NULL)
+    {
+      /* We found a hash table entry for an ancestor, so we stopped scanning
+         since all subdirectories use the same WCROOT.  */
+      *wcroot = found_wcroot;
+    }
+  else if (wc_format == 0)
+    {
+      /* We finally found the database. Construct a wcroot_t for it.  */
+
+      apr_int64_t wc_id;
+      int format;
+      svn_error_t *err;
+
+      err = fetch_sdb_info(&wc_id, &format, sdb, scratch_pool);
+      if (err)
+        {
+          if (err->apr_err == SVN_ERR_WC_CORRUPT)
+            return svn_error_quick_wrapf(
+              err, _("Missing a row in WCROOT for '%s'."),
+              svn_dirent_local_style(original_abspath, scratch_pool));
+          return svn_error_trace(err);
+        }
+
+      /* WCROOT.local_abspath may be NULL when the database is stored
+         inside the wcroot, but we know the abspath is this directory
+         (ie. where we found it).  */
+
+      err = svn_wc__db_pdh_create_wcroot(wcroot,
+                            apr_pstrdup(db->state_pool,
+                                        symlink_wcroot_abspath
+                                          ? symlink_wcroot_abspath
+                                          : local_abspath),
+                            sdb, wc_id, format,
+                            db->verify_format,
+                            db->state_pool, scratch_pool);
+      if (err && (err->apr_err == SVN_ERR_WC_UNSUPPORTED_FORMAT ||
+                  err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) &&
+          kind == svn_node_symlink)
+        {
+          /* We found an unsupported WC after traversing upwards from a
+           * symlink. Fall through to code below to check if the symlink
+           * points at a supported WC. */
+          svn_error_clear(err);
+          *wcroot = NULL;
+        }
+      else if (err)
+        {
+          /* Close handle if we are not going to use it to support
+             upgrading with exclusive wc locking. */
+          return svn_error_compose_create(err, svn_sqlite__close(sdb));
+        }
+    }
+  else
+    {
+      /* We found something that looks like a wc-1 working copy directory.
+         However, if the format version is 12 and the .svn/entries file
+         is only 3 bytes long, then it's a breadcrumb in a wc-ng working
+         copy that's missing an .svn/wc.db, or its .svn/wc.db is corrupt. */
+      if (wc_format == SVN_WC__WC_NG_VERSION /* 12 */)
+        {
+          apr_finfo_t info;
+
+          /* Check attributes of .svn/entries */
+          const char *admin_abspath = svn_wc__adm_child(
+              local_abspath, SVN_WC__ADM_ENTRIES, scratch_pool);
+          svn_error_t *err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
+                                         scratch_pool);
+
+          /* If the former does not succeed, something is seriously wrong. */
+          if (err)
+            return svn_error_createf(
+                SVN_ERR_WC_CORRUPT, err,
+                _("The working copy at '%s' is corrupt."),
+                svn_dirent_local_style(local_abspath, scratch_pool));
+          svn_error_clear(err);
+
+          if (3 == info.size)
+            {
+              /* Check existence of .svn/wc.db */
+              admin_abspath = svn_wc__adm_child(local_abspath, SDB_FILE,
+                                                scratch_pool);
+              err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
+                                scratch_pool);
+              if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+                {
+                  svn_error_clear(err);
+                  return svn_error_createf(
+                      SVN_ERR_WC_CORRUPT, NULL,
+                      _("The working copy database at '%s' is missing."),
+                      svn_dirent_local_style(local_abspath, scratch_pool));
+                }
+              else
+                /* We should never have reached this point in the code
+                   if .svn/wc.db exists; therefore it's best to assume
+                   it's corrupt. */
+                return svn_error_createf(
+                    SVN_ERR_WC_CORRUPT, err,
+                    _("The working copy database at '%s' is corrupt."),
+                    svn_dirent_local_style(local_abspath, scratch_pool));
+            }
+        }
+
+      SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot,
+                            apr_pstrdup(db->state_pool,
+                                        symlink_wcroot_abspath
+                                          ? symlink_wcroot_abspath
+                                          : local_abspath),
+                            NULL, UNKNOWN_WC_ID, wc_format,
+                            db->verify_format,
+                            db->state_pool, scratch_pool));
+    }
+
+  if (*wcroot)
+    {
+      const char *dir_relpath;
+
+      if (symlink_wcroot_abspath)
+        {
+          /* The WCROOT was found through a symlink pointing at the root of
+           * the WC. Cache the WCROOT under the symlink's path. */
+          local_dir_abspath = symlink_wcroot_abspath;
+        }
+
+      /* The subdirectory's relpath is easily computed relative to the
+         wcroot that we just found.  */
+      dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL);
+
+      /* And the result local_relpath may include a filename.  */
+      *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool);
+    }
+
+  if (kind == svn_node_symlink)
+    {
+      svn_boolean_t retry_if_dir = FALSE;
+      svn_wc__db_status_t status;
+      svn_boolean_t conflicted;
+      svn_error_t *err;
+
+      /* Check if the symlink is versioned or obstructs a versioned node
+       * in this DB -- in that case, use this wcroot. Else, if the symlink
+       * points to a directory, try to find a wcroot in that directory
+       * instead. */
+
+      if (*wcroot)
+        {
+          err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL,
+                                              NULL, NULL, NULL, NULL, NULL,
+                                              NULL, NULL, NULL, NULL, NULL,
+                                              NULL, NULL, NULL, &conflicted,
+                                              NULL, NULL, NULL, NULL, NULL,
+                                              NULL, *wcroot, *local_relpath,
+                                              scratch_pool, scratch_pool);
+          if (err)
+            {
+              if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+                  && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
+                return svn_error_trace(err);
+
+              svn_error_clear(err);
+              retry_if_dir = TRUE; /* The symlink is unversioned. */
+            }
+          else
+            {
+              /* The symlink is versioned, or obstructs a versioned node.
+               * Ignore non-conflicted not-present/excluded nodes.
+               * This allows the symlink to redirect the wcroot query to a
+               * directory, regardless of 'invisible' nodes in this WC. */
+              retry_if_dir = ((status == svn_wc__db_status_not_present ||
+                               status == svn_wc__db_status_excluded ||
+                               status == svn_wc__db_status_server_excluded)
+                              && !conflicted);
+            }
+        }
+      else
+        retry_if_dir = TRUE;
+
+      if (retry_if_dir)
+        {
+          svn_node_kind_t resolved_kind;
+
+          SVN_ERR(svn_io_check_resolved_path(original_abspath,
+                                             &resolved_kind,
+                                             scratch_pool));
+          if (resolved_kind == svn_node_dir)
+            {
+              symlink_wcroot_abspath = original_abspath;
+              SVN_ERR(read_link_target(&local_abspath, original_abspath,
+                                       scratch_pool));
+              /* This handle was opened in this function but is not going
+                 to be used further so close it. */
+              if (sdb)
+                SVN_ERR(svn_sqlite__close(sdb));
+              goto try_symlink_as_dir;
+            }
+        }
+    }
+
+  /* We've found the appropriate WCROOT for the requested path. Stash
+     it into that path's directory.  */
+  svn_hash_sets(db->dir_data,
+                apr_pstrdup(db->state_pool, local_dir_abspath),
+                *wcroot);
+
+  /* Did we traverse up to parent directories?  */
+  if (!moved_upwards)
+    {
+      /* We did NOT move to a parent of the original requested directory.
+         We've constructed and filled in a WCROOT for the request, so we
+         are done.  */
+      return SVN_NO_ERROR;
+    }
+
+  /* The WCROOT that we just found/built was for the LOCAL_ABSPATH originally
+     passed into this function. We stepped *at least* one directory above that.
+     We should now associate the WROOT for each parent directory that does
+     not (yet) have one.  */
+
+  scan_abspath = local_dir_abspath;
+
+  do
+    {
+      const char *parent_dir = svn_dirent_dirname(scan_abspath, scratch_pool);
+      svn_wc__db_wcroot_t *parent_wcroot;
+
+      parent_wcroot = svn_hash_gets(db->dir_data, parent_dir);
+      if (parent_wcroot == NULL)
+        {
+          svn_hash_sets(db->dir_data, apr_pstrdup(db->state_pool, parent_dir),
+                        *wcroot);
+        }
+
+      /* Move up a directory, stopping when we reach the directory where
+         we found/built the WCROOT.  */
+      scan_abspath = parent_dir;
+    }
+  while (strcmp(scan_abspath, local_abspath) != 0);
+
+  svn_pool_destroy(iterpool);
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
+svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot,
+                                      const char **local_relpath,
+                                      svn_wc__db_t *db,
+                                      const char *local_abspath,
+                                      apr_pool_t *result_pool,
+                                      apr_pool_t *scratch_pool)
+{
+  return svn_wc__db_wcroot_parse_local_abspath2(wcroot, local_relpath, db,
+                                                local_abspath,
+                                                svn_node_unknown,
+                                                result_pool, scratch_pool);
+}
+
+svn_error_t *
 svn_wc__db_drop_root(svn_wc__db_t *db,
                      const char *local_abspath,
                      apr_pool_t *scratch_pool)

Reply via email to