Hi,

My company uses Subversion via Apache on a Windows Server 2003 R2 machine:

Apache/2.2.17 (Win32) mod_ssl/2.2.17 OpenSSL/0.9.8o SVN/1.6.16 DAV/2
mod_perl/2.0.4-dev Perl/v5.10.1

We're using version 1.6.16 of the Windows binaries from alagazam.net.

Our Apache logs showed that our server was occasionally failing:

[Tue Apr 19 20:33:30 2011] [notice] Parent: child process exited with
status 128 -- Restarting.


To track down the source of the exiting child processes, we attached a
debugger to the Apache child process. Eventually, an exception
occurred:

EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 7c8285f3 (ntdll!KiRaiseUserExceptionDispatcher+0x00000037)
  ExceptionCode: c0000008 (Invalid handle)
 ExceptionFlags: 00000000
NumberParameters: 0


The debugger helpfully pointed to the stack trace of the suspect thread:

ntdll!KiRaiseUserExceptionDispatcher+0x37
ntdll!KiFastSystemCall+0x3
ntdll!ZwClose+0xc
kernel32!CloseHandle+0x59
libapr_1!thread_mutex_cleanup+0x2c [.\locks\win32\thread_mutex.c @ 35]
libapr_1!apr_pool_cleanup_run+0x19 [.\memory\unix\apr_pools.c @ 2299]
libapr_1!apr_thread_mutex_destroy+0x14 [.\locks\win32\thread_mutex.c @ 133]
libapr_1!apr_file_close+0x2f [.\file_io\win32\open.c @ 501]
libsvn_subr_1!svn_io_file_close+0x1b
[C:\SVN\src-1.6.16\subversion\libsvn_subr\io.c @ 2694]
libsvn_repos_1!svn_repos_abort_report+0x12
[C:\SVN\src-1.6.16\subversion\libsvn_repos\reporter.c @ 1343]
mod_dav_svn!dav_svn__update_report+0xea1
[C:\SVN\src-1.6.16\subversion\mod_dav_svn\reports\update.c @ 1495]
mod_dav_svn!deliver_report+0x6f
[C:\SVN\src-1.6.16\subversion\mod_dav_svn\version.c @ 953]
mod_dav!dav_method_report+0xc4
[C:\local0\asf\release\build-2.2.17\modules\dav\main\mod_dav.c @ 4066]
mod_dav!dav_handler+0x1d4
[C:\local0\asf\release\build-2.2.17\modules\dav\main\mod_dav.c @ 4701]
libhttpd!ap_run_handler+0x21 [.\server\config.c @ 158]
libhttpd!ap_invoke_handler+0xb0 [.\server\config.c @ 376]
libhttpd!ap_process_request+0x3e [.\modules\http\http_request.c @ 286]
libhttpd!ap_process_http_connection+0x4c [.\modules\http\http_core.c @ 190]
libhttpd!ap_run_process_connection+0x21 [.\server\connection.c @ 43]
libhttpd!ap_process_connection+0x33 [.\server\connection.c @ 190]
libhttpd!worker_main+0x9c [.\server\mpm\winnt\child.c @ 778]
msvcrt!_endthreadex+0xa3
kernel32!BaseThreadStart+0x34


In libsvn_repos_1!svn_repos_abort_report, I see that it tried to close
a temporary file:

// line 1346 of libsvn_repos\reporter.c.
return svn_io_file_close(b->tempfile, pool);


Going up the call stack, I see how svn_repos_abort_report was called
in dav_svn__update_report:

// Line 1492 of mod_dav_svn\reports\update.c:
if (derr && rbaton)
   svn_error_clear(svn_repos_abort_report(rbaton, resource->pool));


Backtracing through dav_svn__update_report and considering when rbaton
could be non-null, I focused on the following call:

// Line 1379 of mod_dav_svn\reports\update.c:
serr = svn_repos_finish_report(rbaton, resource->pool);


Lo and behold, I noticed the following lines in svn_repos_finish_report:

// Line 1334 of libsvn_repos\reporter.c:
finish_err = finish_report(b, pool);
close_err = svn_io_file_close(b->tempfile, pool);


I suspect I have found the source of the invalid handle exception. The
handle to the tempfile pointed to by b->tempfile is being closed two
times. This only happens when the finish_report function returns any
kind of error.

To recap the code flow:

1. mod_dav_svn!dav_svn__update_report is called

2. mod_dav_svn!dav_svn__update_report calls
libsvn_repos_1!svn_repos_finish_report

3. libsvn_repos_1!svn_repos_finish_report calls libsvn_repos_1!finish_report

4. libsvn_repos_1!finish_report returns an error to
libsvn_repos_1!svn_repos_finish_report

5. libsvn_repos_1!svn_repos_finish_report calls
libsvn_subr_1!svn_io_file_close on a tempfile. The file successfully
closes and the handle to the tempfile is now invalid.

6. libsvn_repos_1!svn_repos_finish_report returns an error to
mod_dav_svn!dav_svn__update_report

7. mod_dav_svn!dav_svn__update_report jumps to the cleanup label.
Notice that the rbaton local variable wasn’t set to null.

8. mod_dav_svn!dav_svn__update_report calls
libsvn_repos_1!svn_repos_abort_report because rbaton are derr aren’t
both null

9. libsvn_repos_1!svn_repos_abort_report calls
libsvn_subr_1!svn_io_file_close on the same tempfile in step 5.

10. An invalid handle exception later occurs.

I see the problem was first noticed years ago on the mailing list
("When to call svn_repos_abort_report?"):

http://svn.haxx.se/dev/archive-2005-07/1176.shtml

However, that thread discusses calls to svn_repos_abort_report from
functions other than dav_svn__update_report.

I believe setting rbaton to NULL immediately after the call to
libsvn_repos_1!svn_repos_finish_report would eliminate the invalid
handle exception. I’m not sure if this is the most elegant solution.
See the attached patch.

[[[
Fix invalid handle exception when temporary file is closed multiple times.
]]]

Thanks,

Chad Nouis
Trapeze Group  /  www.trapezegroup.com
Index: subversion/mod_dav_svn/reports/update.c
===================================================================
--- subversion/mod_dav_svn/reports/update.c     (revision 1095513)
+++ subversion/mod_dav_svn/reports/update.c     (working copy)
@@ -1317,6 +1317,11 @@ dav_svn__update_report(const dav_resource *resourc
   /* this will complete the report, and then drive our editor to generate
      the response to the client. */
   serr = svn_repos_finish_report(rbaton, resource->pool);
+
+  /* We're finished with the report baton.  Note that so we don't try
+     to abort this report later. */
+  rbaton = NULL;
+
   if (serr)
     {
       derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
@@ -1326,10 +1331,6 @@ dav_svn__update_report(const dav_resource *resourc
       goto cleanup;
     }
 
-  /* We're finished with the report baton.  Note that so we don't try
-     to abort this report later. */
-  rbaton = NULL;
-
   /* ### Temporarily disable resource_walks for single-file switch
      operations.  It isn't strictly necessary. */
   if (dst_path && resource_walk)

Reply via email to