Hi, this patch attempts to speed up svn_repos_authz_check_access, esp. when it is called repeatedly during the same HTTP request (or on the same connection). Subversion issues many HTTP requests that result only in a single call to svn_repos_authz_check_access (i.e. just for the path given in the request itself). Others may call svn_repos_authz_check_access lots of times, e.g "svn log" calls it for every modified path in every revision in the requested range of revisions.
This patch reduces the cumulative time for svn_repos_authz_check_access (when called repeteadly in the same connection) for more than 50% (e.g. running the attached test program on a large body of paths). The observations behind the patch are: (1) The functions like authz_get_path_access in libsvn_repos/authz.c that do the actual work all use svn_config_enumerate2(..., path, authz_parse_line, baton, ...) to compute allow/deny information for the given path and user (implicit in baton). The result is computed over and over again, even if the same path and user are specified than in a previous call. (2) In "real life", i.e. in the Apache server, user has always the same value for the same request (even the same connection). The patch augments the svn_authz_t struct wih a field "cache" which is a apr_hash_t, mapping paths to already known allow/deny information. (This cache is obviously correct only for the same value of user, so we store that in another field "cached_user"; if the value of user changes, we simply throw away the existing cache). We only store a "path" in the cache if it has a [path] section in the actual access control file, so the cache cannot grow larger than the apr_hash_t used to store that. The patch then replaces all of the above calls to svn_config_enumerate2 with a wrapper function, that first checks for a cached value. Otherwise it will call svn_config_enumerate2 and cache the result. It also changes the internal functions - authz_get_path_access - authz_get_any_access - authz_get_tree_access to take a svn_authz_t* as the first parameter (instead of svn_config_t*) so that they have access to both the svn_config_t and the cache. The patch needs to #include libsvn_subr/config_impl.h in order to gain access to svn_config_t.pool: the cache (apr_hash_t itself, keys and values) must be allocated from the same pool as svn_config_t so that they have the same lifespan. Cheers, Roderich
libsvn_repos_authz.patch
Description: Binary data
/*
* Usage: test-authz-cache [-c][-n][-s] ACCESS_FILE USER
*
* Load the ACCESS_FILE and run test for USER reading paths from stdin
* -c cache authz information (default is not to cache)
* -n don't lookup authz, just read the paths (for timing)
* -s show cache contents at program end
*
* Run svn_repos_authz_check_access on a list of paths read from stdin
* using an access file and user given as arguments.
*
* Stdin should contain one path (with a leading slash) perl line,
* optionally separating revisions by a line starting with "-"
* (this may affect pool handling). One way to create such list is:
*
* svn log --verbose --xml ... | \
* sed -n -e 's|^<logentry.*|-|p' -e 's|^ *action=".">\(.*\)</path>|\1|p'
*
* Linking: -lsvn_repos -lsvn_subr -lapr-N
*/
#include "svn_pools.h"
#include "svn_repos.h"
#include "svn_cmdline.h"
/* FIXME must keep these consistent with subversion/libsvn_repos/authz.c */
extern svn_boolean_t authz_use_cache; /* FIXME devel only */
struct svn_authz_t
{
svn_config_t *cfg;
const char *cached_user;
apr_hash_t *cache;
};
struct access_cache_t
{
svn_repos_authz_access_t allow;
svn_repos_authz_access_t deny;
};
static svn_boolean_t
display_authz_cache(const char *path, void *baton, apr_pool_t *pool)
{
struct access_cache_t *cached = apr_hash_get((apr_hash_t*)baton, path, APR_HASH_KEY_STRING);
if (!cached)
printf("[%s] not cached\n", path);
else
printf("[%s] allow=%d deny=%d\n", path, cached->allow, cached->deny);
return TRUE;
}
static void
usage(int argc, const char **argv)
{
printf("Usage: %s [-c][-n][-s] ACCESS_FILE USER\n\n", argv[0]);
printf("Load the ACCESS_FILE and run test for USER reading paths from stdin\n"
" -c cache authz information\n"
" -n don't lookup authz, just read the paths (for timing)\n"
" -s show cache contents at program end\n");
exit(2);
}
int
main(int argc, const char **argv)
{
apr_pool_t *global_pool, *pool;
svn_error_t *err;
struct svn_authz_t *authz;
const char *access_file;
const char *user;
char path[1024];
svn_boolean_t use_cache = FALSE;
svn_boolean_t show_cache = FALSE;
svn_boolean_t no_run = FALSE;
int optind = 0;
int npaths = 0;
int nrevs = 0;
int granted = 0;
int denied = 0;
while (++optind < argc)
{
if (strcmp(argv[optind], "-c") == 0)
{ use_cache = TRUE; continue; }
if (strcmp(argv[optind], "-n") == 0)
{ no_run = TRUE; continue; }
if (strcmp(argv[optind], "-s") == 0)
{ show_cache = TRUE; continue; }
break;
}
if (argc - optind != 2)
usage(argc, argv);
access_file = argv[optind++];
user = argv[optind++];
/* Initialize the app. Send all error messages to 'stderr'. */
if (svn_cmdline_init(argv[0], stderr) != EXIT_SUCCESS)
exit(2);
global_pool = svn_pool_create(NULL);
/* Read the access file and validate it. */
if (err = svn_repos_authz_read(&authz, access_file, TRUE, global_pool))
{
svn_handle_error2(err, stderr, FALSE, "test-monty authz_read: ");
exit(1);
}
authz_use_cache = use_cache;
pool = svn_pool_create(global_pool); /* create per-rev sub pool */
while (fgets(path, sizeof(path), stdin))
{
char *endp = path + strlen(path) - 1;
svn_boolean_t access_granted;
if (*endp == '\n') *endp-- = '\0'; /* chomp */
if (path[0] == '-') /* separator line */
{
nrevs++;
svn_pool_clear(pool); /* clear sub pool */
continue;
}
npaths++;
if (no_run) continue;
if (err = svn_repos_authz_check_access(authz, "", path, user,
svn_authz_read, &access_granted, pool))
{
svn_handle_error2(err, stderr, FALSE, "test-monty authz_check_access: ");
exit(1);
}
if (access_granted) granted++;
else denied++;
}
svn_pool_destroy(global_pool);
printf("\nprocessed %d revs, %d paths\n", nrevs, npaths);
if (!no_run)
{
printf("%s: %d granted, %d denied\n",
use_cache ? "using cache": "NOT using cache", granted, denied);
/* show cached info */
if (use_cache && show_cache)
{
printf("\ncached sections:\n");
svn_config_enumerate_sections2(authz->cfg, display_authz_cache,
authz->cache, global_pool);
}
}
exit(0);
}

