> -----Ursprüngliche Nachricht-----
> Von: Thomas Hoffmann (Speed4Trade GmbH)
> <thomas.hoffm...@speed4trade.com.INVALID>
> Gesendet: Mittwoch, 15. Juni 2022 08:26
> An: Tomcat Users List <users@tomcat.apache.org>
> Betreff: AW: Filehandle left open when using sendfile
> 
> Hello Christopher,
> 
> > -----Ursprüngliche Nachricht-----
> > Von: Christopher Schultz <ch...@christopherschultz.net>
> > Gesendet: Dienstag, 14. Juni 2022 20:26
> > An: users@tomcat.apache.org
> > Betreff: Re: Filehandle left open when using sendfile
> >
> > Thomas,
> >
> > On 6/14/22 13:52, Thomas Hoffmann (Speed4Trade GmbH) wrote:
> > > Hello,
> > > we are using Tomcat 10.0.16 under windows.
> > > For sending files to the browser, we are using sendfile by setting
> > > the
> > attribute "org.apache.tomcat.sendfile.filename".
> > > Streaming an image to the browser works well in this way.
> > > But we observed that if the user tries to delete the file
> > > afterwards, there is
> > still a file-handle on this file.
> >
> > Which user? Some admin on the server, or the user using the browser?
> > (Dumb question, I know, but tb and ff had a bug for a while where
> > downloads would leave dangling file handles, disallowing deleting
> > those files after they were downloaded.)
> >
> > > I took a look at NioEndpoint.java -->
> > >
> >
> https://github.com/apache/tomcat/blob/main/java/org/apache/tomcat/util
> > > /net/NioEndpoint.java
> > >
> > > At line 869 there is a stream opened:
> > > FileInputStream fis = new FileInputStream(f);
> > >
> > > However, there is no close in this method. Maybe this might cause
> > > the
> > open file-handle?
> >
> > Did you read the line above that?
> >
> >      @SuppressWarnings("resource") // Closed when channel is closed
> >      FileInputStream fis = new FileInputStream(f);
> >
> > The channel itself is cleaned-up properly and, with it, the file handle.
> >
> >
> https://github.com/apache/tomcat/blob/69b9a3eeee41062dc1930782941bb
> > aa1880e9281/java/org/apache/tomcat/util/net/NioEndpoint.java#L1226
> >
> > (At least, I *think* this is where the cleanup happens. I'm not very
> > well- versed in the connection-handling code in Tomcat.)
> >
> > > We are using protocol="org.apache.coyote.http11.Http11NioProtocol"..
> > > I
> > think there are different implementations for sendfile-feature.
> > > The open file-handle can be observer via ProcessExplorer from Microsoft.
> >
> > How long did you wait for the file handle to close?
> >
> > Are you able to attach a debugger and see that the object hasn't been
> > cleaned-up?
> >
> > -chris
> >
> 
> Thanks for taking a look at it.
> Sorry, if the setup was not described in detail. Maybe you can imagine an
> image library, hosted server-side.
> The user can upload pics and after upload the user can also decide to delete
> the image.
> 
> Requests:
> 1) POST-Request with image upload. Server saves the image in the file
> system and returns the URL.
> 2) GET-Request, the browser requests the URL of the uploaded picture.
> Server is sending the file via sendfile
> 3) POST-Request with command to delete the picture (maybe user doesn’t
> like the pic). Tomcat tries to delete the file on the server side but fails
> because of the left handle in step 2
> 
> The handle stays for several minutes for sure.
> 
> It is hard to debug because when stepping through, the file-handle is gone
> after processing the request.
> I think there are some if-statements whether the transmission is pending or
> finished.
> The mentioned line in the NIOEndpoint is not reached. So the file handle
> must be left somewhere else. My first assumption was wrong.
> 
> Greetings, Thomas

Short update on this issue:
Today, I could further debug the issue.

Within Http2AsyncUpgradeHandler.java the following code creates the file handle:

    protected SendfileState processSendfile(SendfileData sendfile) {
        if (sendfile != null) {
            try {
                try (FileChannel channel = FileChannel.open(sendfile.path, 
StandardOpenOption.READ)) {
                    sendfile.mappedBuffer = channel.map(MapMode.READ_ONLY, 
sendfile.pos, sendfile.end - sendfile.pos);
                }
    ......

Even with the try-with-resource block, the file handle is still there after 
this code block.
Sometimes the handle vanishes quite quickly, sometimes the handle stays open 
for many minutes.

It can be verified with a  little jsp sample page:
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>
<%@page import="org.apache.coyote.Constants"%>
<%
    String fileName = "c:\\temp\\test.png";
    File file = new File(fileName);
    long l = file.length();
    request.setAttribute(Constants.SENDFILE_FILENAME_ATTR, 
file.getAbsolutePath());
    request.setAttribute(Constants.SENDFILE_FILE_START_ATTR, 0L);
    request.setAttribute(Constants.SENDFILE_FILE_END_ATTR, l);
    response.setHeader("Content-Length", Long.toString(l));
    String contentType = URLConnection.guessContentTypeFromName(fileName);
    if (contentType == null)
    {
        contentType = "application/octet-stream";
    }
    response.setContentType(contentType);
    response.flushBuffer();
%>

The connector looks like:
        <Connector port="443" 
protocol="org.apache.coyote.http11.Http11NioProtocol"
               
sslImplementationName="org.apache.tomcat.util.net.openssl.OpenSSLImplementation"
               maxThreads="150" minSpareThreads="25"    URIEncoding="UTF-8" 
useBodyEncodingForURI="false"
               enableLookups="false" disableUploadTimeout="true"     
acceptCount="100" scheme="https" secure="true"   SSLEnabled="true"    
compression="force">
                <UpgradeProtocol 
className="org.apache.coyote.http2.Http2Protocol"  />
                <SSLHostConfig 
ciphers="ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
                                disableSessionTickets="true"    
honorCipherOrder="false"      protocols="+TLSv1.2,+TLSv1.3">
                    <Certificate certificateKeyFile="private.key"   
certificateFile="public.pem"    type="RSA"    />
                </SSLHostConfig>
    </Connector>

Greetings, 
Thomas

Reply via email to