Index: subversion/libsvn_ra_svn/client.c =================================================================== --- subversion/libsvn_ra_svn/client.c (revision 35631) +++ subversion/libsvn_ra_svn/client.c (working copy) @@ -26,6 +26,12 @@ #include #include #include +#if APR_HAVE_STDLIB_H +#include +#endif +#if APR_HAVE_UNISTD_H +#include +#endif #include "svn_types.h" #include "svn_string.h" @@ -426,6 +432,43 @@ svn_error_clear(svn_ra_svn_flush(conn, pool)); } +static apr_status_t detach_child_cleanup(void *data) +{ +#if APR_HAVE_STDLIB_H +#if APR_HAVE_UNISTD_H +#if APR_HAS_FORK + /* Using apr_procattr_detach_set has a number of undesirable + * side effects: + * - Redirecting stdin/stdout/stderr to /dev/null + * - Detaching from the controlling terminal + * + * We can workaround the redirections with some calls to + * dup/dup2/fclose in a child cleanup function. However + * that ends up leaving the child with 3 extra open file + * handles to /dev/null -- they're not used, but it's not + * clean. + * + * Unfortunately the side effect of detaching from the + * controlling terminal is that any tunnel processes that + * may need to prompt for a password will get confused + * (i.e. ssh). So instead we just do the detach ourselves + * here and avoid using apr_procattr_detach_set. + * + * Since child_cleanup functions only run on systems with + * fork this code is surrounded with the appropriate + * #ifdefs. On systems without fork, the code here + * isn't needed anyway. + */ + int x; + if ((x = fork()) > 0) { + exit(0); + } +#endif +#endif +#endif + return APR_SUCCESS; +} + /* (Note: *CONN is an output parameter.) */ static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn, apr_pool_t *pool) @@ -433,6 +476,7 @@ apr_status_t status; apr_proc_t *proc; apr_procattr_t *attr; + apr_pool_t *temp_pool = NULL; status = apr_procattr_create(&attr, pool); if (status == APR_SUCCESS) @@ -441,12 +485,36 @@ status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); if (status == APR_SUCCESS) status = apr_procattr_child_errfn_set(attr, handle_child_process_error); + if (status == APR_SUCCESS) + status = apr_pool_create(&temp_pool, NULL); + if (status == APR_SUCCESS) + apr_pool_cleanup_register(temp_pool, NULL, apr_pool_cleanup_null, + detach_child_cleanup); proc = apr_palloc(pool, sizeof(*proc)); if (status == APR_SUCCESS) status = apr_proc_create(proc, *args, args, NULL, attr, pool); + if (temp_pool) + apr_pool_destroy(temp_pool); if (status != APR_SUCCESS) return svn_error_wrap_apr(status, _("Can't create tunnel")); + /* Arrange for the tunnel agent to get a SIGKILL on pool + * cleanup. This is a little extreme, but the alternatives + * weren't working out: + * - Closing the pipes and waiting for the process to die + * was prone to mysterious hangs which are difficult to + * diagnose (e.g. svnserve dumps core due to unrelated bug; + * sshd goes into zombie state; ssh connection is never + * closed; ssh never terminates). + * - Killing the tunnel agent with SIGTERM leads to unsightly + * stderr output from ssh. + * Since we now run the tunnel detached, this is the best way + * to avoid zombies without adding any wait delays. The actual + * tunnel itself won't get the signal because it will be detached + * by then. + */ + apr_pool_note_subprocess(pool, proc, APR_KILL_ALWAYS); + /* APR pipe objects inherit by default. But we don't want the * tunnel agent's pipes held open by future child processes * (such as other ra_svn sessions), so turn that off. */