Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File-like objects are closed after uploading #80

Open
JordonPhillips opened this issue Dec 29, 2016 · 27 comments
Open

File-like objects are closed after uploading #80

JordonPhillips opened this issue Dec 29, 2016 · 27 comments
Labels

Comments

@JordonPhillips
Copy link
Contributor

Source boto/boto3#929

This is because we wrap them with the InterruptReader which closes them on exit.

@kyleknap
Copy link
Member

Yeah looks like it will only happen for the non-multipart upload case because the provided file object is used. Will probably have to figure out a way to safeguard against accidentally closing the file.

@ramurti
Copy link

ramurti commented Feb 15, 2017

Any workaround to prevent the file being closed? I'm using this function with unnamed temporary files, so I can't simply reopen the file. I'd like to do something else with this file (for example, rotate it to local disk) in case upload_fileobj fails, but it closes the file.... (sigh)

@micahfay
Copy link

micahfay commented Mar 1, 2017

The workaround by leonsmith here worked for me- matthewwithanm/django-imagekit#391

@ofassley
Copy link

Same issue here. I'm also using Imagekit.

self = <ImageCacheFile: CACHE/avatars/a0cbd7767bf54e649f4952e8c3407418/9dd51a8dd80342ae7576d1c376b51e43.jpg>

    def _generate(self):
        # Generate the file
        content = generate(self.generator)

        actual_name = self.storage.save(self.name, content)

        # We're going to reuse the generated file, so we need to reset the pointer.
>       content.seek(0)
E       ValueError: I/O operation on closed file.

actual_name = 'CACHE/avatars/a0cbd7767bf54e649f4952e8c3407418/9dd51a8dd80342ae7576d1c376b51e43.jpg'
content    = <File: None>
self       = <ImageCacheFile: CACHE/avatars/a0cbd7767bf54e649f4952e8c3407418/9dd51a8dd80342ae7576d1c376b51e43.jpg>

../../../.virtualenvs/myapp/lib/python3.5/site-packages/imagekit/cachefiles/__init__.py:102: ValueError

@mgrdcm
Copy link

mgrdcm commented Apr 23, 2017

I can't tell - is this a bug? I'll be happy to take a stab at a PR if it is, and if nobody else wants to handle it. I'm also suffering from the django-imagekit issue.

@micahfay
Copy link

micahfay commented Apr 24, 2017

I've stopped using django-storages and moved to this for django-s3 stuff- lighter weight, and seems to be better supported. Also, it doesn't require that workaround :) https://github.com/etianen/django-s3-storage switching was easy even with existing data.

@joshma
Copy link

joshma commented Oct 18, 2017

@kyleknap when you get a chance, could you chime in on whether calling self._fileobj.close() in InterruptReader is necessary? We're running into the same issue, and it's particularly difficult because we're accepting a few kinds of file-like objects so we can't just re-open it later.

@freemanlevinetang
Copy link

Ran into this issue recently while attempting to generate images, upload them to s3 for backup, and then attach them to an email.
Workaround was to pull the file back down from s3 after uploading, which seems pretty inefficient.

@leogzyl
Copy link

leogzyl commented Apr 12, 2019

I'm using the following workaround:

from io import BufferedReader

class NonCloseableBufferedReader(BufferedReader):
    def close(self):
        self.flush()

buffer = NonCloseableBufferedReader(myfile)
client.upload_fileobj(Fileobj=buffer, Bucket=bucket, Key=name)
buffer.detach()
assert not myfile.closed

@zachliu
Copy link

zachliu commented Jun 3, 2019

@mgrdcm I think it is a bug. The control should be relinquished to the end user instead of closing it prematurely.

@zachliu
Copy link

zachliu commented Jun 3, 2019

@leogzyl A near perfect workaround except mypy complains if I pass in tempfile.TemporaryFile() object:

error: Argument 1 to "NonCloseableBufferedReader" has incompatible type "IO[Any]"; expected "RawIOBase"

Any solutions other than # type: ignore?

@ncvc
Copy link

ncvc commented Dec 5, 2019

@zachliu I added an init method with a cast to work around the mypy issue:

class NonCloseableBufferedReader(BufferedReader):
    def __init__(self, raw: IO[bytes], *args: Any, **kwargs: Any):
        super().__init__(cast(RawIOBase, raw), *args, **kwargs)

    def close(self) -> None:
        self.flush()

NonCloseableBufferedReader(myfile)  # No mypy errors

@al-muammar
Copy link

3 years passed... Can it be either fixed or declared to be not a bug, so other libraries should build workarounds for it?

@jannotti
Copy link

jannotti commented Jan 9, 2020

I mean, it's clearly a bug, right? You can't specify that a method maybe, sometimes, occasionally closes your files.

@arthurzenika
Copy link

We're affected by this too. The NonCloseableBufferedReader workaround seems to do the trick, but hopefully we can remove it once this is fixed.

@rooom13
Copy link

rooom13 commented Mar 17, 2020

The issue still appears on March 2020, @leogzyl 's workarounds solves the issue

@zivkovic
Copy link

There is many of us with this issue, something should be done with it.

@diego200052
Copy link

I got this same issue on April 2021 using boto3 and upload_fileobj() function.

@jvanasco
Copy link

jvanasco commented Jun 30, 2021

This behavior is not documented. This behavior is not expected. The first version of boto did not implement this behavior. I've been using Python for over 20 years and have never seen a library close a file in this context. There is no reason why this function should close the file.

The only thing more ridiculous than this bug existing, is that it has not been fixed in 4.5 years. If keeping the file open causes an issue, then the library has serious underlying problems.

Aside from the numerous issues this creates with on-disk tempfiles (as one other user mentioned above) which can be nearly impossible to re-open, file-like objects in Python routinely include SpooledTemporaryFile and StringIO objects. This absurd, unexpected and undocumented behavior completely destroys these file-like objects and renders them unrecoverable.

@iamahuman
Copy link

iamahuman commented Sep 21, 2021

Suspected regression commit: 2f3d12c

Diff: 2f3d12c#diff-fb5195c2216d4f5a452b31d518beae30b39667b0655d8a8fd1663a183575aa0aL167-R165

-        client.put_object(Bucket=bucket, Key=key, Body=body, **extra_args)
+        with osutil.open_file_chunk_reader(
+                fileobj, 0, size, progress_callbacks) as body:
+            client.put_object(Bucket=bucket, Key=key, Body=body, **extra_args)

This is because we wrap them with the InterruptReader which closes them on exit.

It appears that InterruptReader has nothing to do with the issue itself. It just so happens that its close() method is in the traceback. Also, patching the issue that way won't last if InterruptReader gets replaced with something else or removed in the future.

Put another way, InterruptReader is just a simple wrapper around a file object, and is not the right abstraction layer to put exceptional logic such as ignoring file close requests (which violates Python's file-like object wrapper protocol).

@saranglakare
Copy link

5 years anniversary of this issue and it's still open! I just wasted a couple of hours figuring out my problem and it came to this bug! Why isn't anyone working on this?

@mgrdcm
Copy link

mgrdcm commented Jan 14, 2023

5 years anniversary of this issue and it's still open! I just wasted a couple of hours figuring out my problem and it came to this bug! Why isn't anyone working on this?

It's tradition now.

@Natgho
Copy link

Natgho commented May 4, 2023

Tradition is continue :(

@yswtrue
Copy link

yswtrue commented Sep 30, 2023

Same here

@antoniosarosi
Copy link

@saranglakare Almost 7 years and still counting 😂, just run into this issue today.

@sangshuduo
Copy link

I encountered this bug on May 3, 2024.

@1mamute
Copy link

1mamute commented May 24, 2024

Kinda disappointing this issue is almost 8 years old and not even a single mention in the documentation was made.

Here's another hacky workaround:

def patched_close():
    print("good try boto3")

file = BytesIO()

# Save the original close function
original_close = file.close

# Patch the close function with ours
file.close = patched_close

client.upload_fileobj(Fileobj=file, Bucket=bucket, Key=name)

# Unpatch it
file.close = original_close

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests