Opened 7 years ago

Closed 7 years ago

Last modified 4 years ago

#8622 closed (fixed)

Exceptions in UploadHandler cause hang

Reported by: kmtracey Owned by: nobody
Component: File uploads/storage Version: master
Severity: Keywords:
Cc: Triage Stage: Unreviewed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:


I ran across this trying to recreate another problem. On Windows, if I set FILE_UPLOAD_TEMP_DIR to reside on a disk that is nearly out of space, and then use the django.core.files.uploadhandler.TemporaryFileUploadHandler to handle uploaded files, file uploads will hang. I changed the implementation of write in the Windows implementation of TemporaryFile in django/core/files/ to be:

        def write(self, s):
            print 'in TemporaryFile(nt), about to call self.file.write(s)'
            x = self.file.write(s)
            print 'in TemporaryFile(nt), back from self.file.write(s)'
            return x

and then when I try the upload (using dev server) I see the first print, but not the second. It's not in a hard loop, the CPU is mostly idle, so it's like its waiting for free space. Clearing space on the disk doesn't wake it up, though.

Attachments (2)

file_upload_hang_test.diff (4.5 KB) - added by vung 7 years ago.
file_upload_error_handling.diff (4.2 KB) - added by vung 7 years ago.

Download all attachments as: .zip

Change History (10)

comment:1 Changed 7 years ago by kmtracey

  • milestone set to 1.0
  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Summary changed from Windows TemporaryFile hangs on write when disk is full to Exceptions in UploadHandler cause hang

Further investigation shows the write is not hanging, it is raising an exception. If I change the code to:

        def write(self, s):
            print 'in TemporaryFile(nt), about to call self.file.write(s)'
                x = self.file.write(s)
            except Exception, e:
                print 'Caught exception on self.file.write(s): %s' % str(e)
            print 'in TemporaryFile(nt), back from self.file.write(s)'
            return x

and try to upload to a disk with not enough free space, I see:

in TemporaryFile(nt), about to call self.file.write(s)
Caught exception on self.file.write(s): [Errno 28] No space left on device

in my dev server console output. No error is reflected anywhere I can see, there is no further console output and the browser page that initiated the upload is stuck at "Loading....".

Changing milestone to 1.0 since this appears to be a more far-reaching problem than I initially thought and someone should probably take a look at it sooner rather than later.

Also exceptions in upload handlers causing requests to hang has been reported here:

comment:2 Changed 7 years ago by mtredinnick

Karen, what's the full stack trace leading to that exception? traceback.print_stack() will be handy there.

comment:3 Changed 7 years ago by kmtracey

Here it is:

in TemporaryFile(nt), about to call self.file.write(s)
Caught exception on self.file.write(s): [Errno 28] No space left on device
  File "d:\u\kmt\django\trunk\django\core\management\commands\", line 54, in inner_run
    run(addr, int(port), handler)
  File "d:\u\kmt\django\trunk\django\core\servers\", line 665, in run
  File "d:\bin\Python2.5.2\lib\", line 201, in serve_forever
  File "d:\bin\Python2.5.2\lib\", line 222, in handle_request
    self.process_request(request, client_address)
  File "d:\bin\Python2.5.2\lib\", line 241, in process_request
    self.finish_request(request, client_address)
  File "d:\bin\Python2.5.2\lib\", line 254, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "d:\u\kmt\django\trunk\django\core\servers\", line 557, in __init__
    BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
  File "d:\bin\Python2.5.2\lib\", line 522, in __init__
  File "d:\u\kmt\django\trunk\django\core\servers\", line 602, in handle
  File "d:\u\kmt\django\trunk\django\core\servers\", line 277, in run
    self.result = application(self.environ, self.start_response)
  File "d:\u\kmt\django\trunk\django\core\servers\", line 634, in __call__
    return self.application(environ, start_response)
  File "d:\u\kmt\django\trunk\django\core\handlers\", line 222, in __call__
    response = self.get_response(request)
  File "d:\u\kmt\django\trunk\django\core\handlers\", line 86, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
  File "d:\u\kmt\django\trunk\django\contrib\auth\", line 67, in __call__
    return self.view_func(request, *args, **kwargs)
  File "D:\u\kmt\software\web\xword\..\xword\crossword\", line 51, in upload_puzzle
    form = UploadPuzzleForm(request.POST, request.FILES)
  File "d:\u\kmt\django\trunk\django\core\handlers\", line 152, in _get_post
  File "d:\u\kmt\django\trunk\django\core\handlers\", line 130, in _load_post_and_files
    self._post, self._files = self.parse_file_upload(self.META, self.environ['wsgi.input'])
  File "d:\u\kmt\django\trunk\django\http\", line 124, in parse_file_upload
    return parser.parse()
  File "d:\u\kmt\django\trunk\django\http\", line 205, in parse
  File "d:\u\kmt\django\trunk\django\core\files\", line 142, in receive_data_chunk
  File "d:\u\kmt\django\trunk\django\core\files\", line 89, in write
    def write(self, s):             return self._file.write(s)
  File "d:\u\kmt\django\trunk\django\core\files\", line 60, in write

comment:4 Changed 7 years ago by anonymous

Karen mentioned it above that there are general problems with exceptions in FileUploadHandlers.
When I raise an Exception in my custom File UploadHandler, than the server gets stuck.

comment:5 Changed 7 years ago by vung

request.POST is computed the first time it is accessed and cached in request._post; if request._post is missing, the request.POST accessor starts parsing POST data.

Parsing POST data means that a django.http.MultiPartParser is instantiated and starts reading from the socket.

If an exception is raised in the meantime, request._post is not set and any access to request.POST will trigger again the parsing.

This second parsing happens when the exception is handled, in django.core.handlers.base.BaseHandler.handle_uncaught_exception, which calls repr(request), etc.

A second MultiPartParser is instantiated and since the first parser already consumed some bytes this second one will never get the chance to read up to content_length, so it will block waiting for content -- the exact place is

I made a small test which reproduces the problem (no solution, though)

Changed 7 years ago by vung

Changed 7 years ago by vung

comment:6 Changed 7 years ago by vung

  • Has patch set

Since the error is caused by triggering again POST data parsing, it would make sense for the fix to try to prevent it somehow; this is what the attached patch does.

Unrelated, but maybe useful for someone who (like me, until now) doesn't know: if an error is elusive, set DEBUG_PROPAGATE_EXCEPTIONS to True in

comment:7 Changed 7 years ago by jacob

  • Resolution set to fixed
  • Status changed from new to closed

(In [8748]) Fixed #8622: accessing POST after a POST handling exception no longer throws the server into an infinite loop. Thanks to vung for tracking this one down and fixing it.

comment:8 Changed 4 years ago by jacob

  • milestone 1.0 deleted

Milestone 1.0 deleted

Note: See TracTickets for help on using tickets.
Back to Top