开发者

What could cause FileStream to read 0 bytes from an apparently locked file?

I have a 'pack files' method, written in J# that takes a bunch of files and writes them all into a stream, along with some enough metadata that they can be reconstructed as individual files by a related 'unpack' method. I've provided a rough version at the end of this question that gives a good indication of what the underlying J# does. I obtained this by running .NET reflector over the J# code to generate the equivalent J# libraries.

Now the code below works perfectly in developement, but intermittently experiences errors in production. The exceptions labelled in the comments below as 'Error 1' and 'Error 2' have both been seen in the wild. In the case where 'ERROR 2' occurs, there are always 0 bytes written. There is no apparent pattern to when these errors occur. The file sizes involved are typically under 100kb.

The files that get passed into the 'pack' method have very recently been created, as i开发者_JS百科n milliseconds ago, wall-clock time. The output stream points at a file which has newly been created with no sharing.

So to summarize, sometimes I get a file length of '0' returned for a file that I know exists... because I just created it. Due to the involvement of J# I cannot obtain the actual exception. (Of course if I was debugging I could break on the first chance exception, but as mentioned this never happens in the development environment). Other times I am unable to read any bytes out of the file, even though it has been successfully opened. There is exception during the copy process, I can only assume that 'Read' returned -1.

What could be going on here? Any ideas? My suspicion is that there is a virus checked running in Prod, but not dev, and that maybe it's involved somehow. But what could a virus checker do when I have a file open and locked (as in the WriteToStream method) that would cause it to stop reading without an error? I have written up a little test app that can lock arbitrary files... but locking the file from another process doesn't seem to stop FileInfo.Length from working, and once the file stream opens, my test app can no longer lock the file, as you would expect.

I'm stumped, I am.

EDIT:

Okay - here is the J# code instead. Have re-tagged the question.

EDIT 2:

I should also mention that the check for 0 length was added later for trouble-shooting purposes. Prior to that, it was always just failing after comparing 'length' to 'written'. So whenever 'length' does not equal 'written', sometimes 'length' is 0 and sometimes 'written' is 0. I am confident the problem is NOT a bug in my code, but is caused by something external. The purpose of this question is to find out what another process (e.g. a virus checker) could do to those files to cause my code to fail in the way I describe.

public static void packContentsToStream(Serializable serializable, Stream stream)
{
    try
    {
        OutputStream output = new StreamWrapperOutputStream(stream);

        FileRecordPair[] recordPairs = SerializationUtil.getRecords(serializable);
        FileRecord[] records = new FileRecord[recordPairs.length];
        File[] files = new Files[recordPairs.length];

        for (int i = 0; i < recordPairs.length; i++)
        {
            FileRecordPair pair = recordPairs[i];
            records[i] = pair.getRecord();
            files[i] = pair.getFile();
        }

        SerializationUtil.writeToStream(serializable, output, false); // False keeps stream open
        SerializationUtil.writeToStream(records, output, false);

        for (int i = 0; i < files.length; i++)
        {
            File file = files[i];
            long written = writeToStream(file, output);
            if (written != records[i].getFileLength())
            {
                throw new SystemException("Invalid record. The number of bytes written [" + written + "] did not match the recorded file length [" + records[i].getFileLength() + "]."); // ERROR 2
            }
        }
     }
     catch (Exception e)
     {
        throw new SystemException("Could not write FileRecords", e);
     }
}


public static long writeToStream(HapiFile file, OutputStream stream)
{
    long written = 0;
    if (file.exists())
    {
        FileInputStream fis = null;
        try
        {
            fis = new FileInputStream(file);
            written = copy(fis, stream);
        }
        catch (Exception e)
        {
            throw new SystemException("Could not write file to stream", e);
        }
        finally
        {
            if (fis != null)
            {
                try
                {
                    fis.close();
                }
                catch (IOException ioe)
                {
                    // For now - throw an exception to see if this might be causing the packing error
                    throw new SystemException("Error closing file", ioe);
                }
            }
        }
    }

    return written;
}

public static int copy(InputStream is, OutputStream stream) throws IOException
{
    int total = 0;
    int read = 0;
    byte[] buffer = new byte[BUFFER_SIZE];

    while (read > -1)
    {
        read = is.read(buffer);
        if (read > 0)
        {
            stream.write(buffer, 0, read);
            total += read;
        }
    }

    return total;
}

// Relevant part of 'SerializationUtil.getRecords'

private static FileRecord GetFor(File file, String recordName, int index, String pathToInstance) 
{
    String fileName = file.getName();
    int length = (int) file.length(); // Safe as long as file is under 2GB

    if (length == 0) 
    {
        throw new SystemException("Could not obtain file length for '" + file.getPath() + "'"); // ERROR 1
    }

    if (index > -1 && recordName != null && recordName.length() > 0) 
    {
        recordName = recordName + "." + index;
    }

    return new FileRecord(fileName, length, recordName, pathToInstance);
}

// File.length() implementation - obtained using .NET Reflector
public virtual long length()
{
    long length = 0L;
    if (this.__fileInfo != null)
    {
        SecurityManager manager = System.getSecurityManager();
        if (manager != null)
        {
            manager.checkRead(this.__mCanonicalPath);
        }
        try
        {
            this.__fileInfo.Refresh();
            if (!this.exists() || !(this.__fileInfo is FileInfo))
            {
                return 0L;
            }
            length = ((FileInfo) this.__fileInfo).Length;
        }
        catch (FileNotFoundException)
        {
        }
        catch (IOException)
        {
        }
    }
    return length;
}


WriteToStream method. Please close the file first. Always use finally block there.


Never mind... One of the objects in the C# code that was calling the J# code had a finalizer that deleted the file the J# code relied on. The J# code was fine. The virus checker was not to blame. We just had a saboteur in the ranks. The garbage collector was coming in and collecting an object that still appeared to be in scope.


use the refresh function to update your file's info

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜