3

I have a Perl application which writes logs to a file using open and print calls.

open (FH, "d:\\temp.txt");
print FH "Some log";
close (FH);

However during an abrupt shutdown of the machine, the logs are not persisted to the file. So after searching at several places, two options were suggested for doing unbuffered IO (i.e writing the text to the disk instead of maintaining it in cache and then flushing it):

  1. sysopen, syswrite
  2. $| = 1;

I have tried both these options and it just doesn't work. Any write that I do seconds before the abnormal shutdown gets lost.

Is there any way that I can almost deterministically accomplish unbuffered IO in Perl? I am running Windows 7 64-bit with Perl 5.8.3.

EDIT: I searched for how to have windows perform unbuffered IO and this is how it can be done! Call

  1. CreateFile with FILE_FLAG_NO_BUFFERING for the dwFlagsAndAttributes parameter. However, this has memory alignment issues to consider (i.e File Access buffers should be sector aligned; An application determine the sector size by calling GetDiskFreeSpace)
  2. Use WriteFile to write data to the file. This write would be unbuffered and instead of going to the cache, it straightaway goes to the disk.
  3. Finally, call FlushFileBuffers to flush the metadata associated with the files.

Could someone please assist with the Win32 APIs from Perl for these 3 calls.

ikegami
  • 367,544
  • 15
  • 269
  • 518
Santhosh
  • 6,547
  • 15
  • 56
  • 63
  • Try adding a `\n` to your print and using `$| = 1`. – a'r Mar 09 '11 at 07:19
  • `$|` only sets autoflush on the currently selected filehandle (STDOUT by default), not all filehandles – Cameron Mar 09 '11 at 07:36
  • `\n` would never help for a handle that's not attached to a terminal. `\n` would not be needed for a handle that has autoflush (`$|=1`). – ikegami Mar 09 '11 at 07:40
  • I'm impatiently waiting for feedback on my latest answer! – ikegami Mar 14 '11 at 16:18
  • 1
    @ikegami, Sorry for the late response! Thanks for your solution, I have accepted it! It just works!!! – Santhosh Mar 16 '11 at 13:51
  • @Santhosh, Awesome! Good to hear, thanks. I would like for the handle to be more natural (no explicit close needed), but that would have required way more code than I was willing to put forth. Enjoy – ikegami Mar 18 '11 at 08:05

3 Answers3

6
use IO::Handle;
open(FH, "d:\\temp.txt");
FH->autoflush(1);
print FH "Some log";
close(FH);

That will get it out to the OS ASAP, but the OS might take a while to commit it to disk. Still I'm sure you'll find this will suit your needs.

If you were on unix, I'd refer to you sync for more info on having the OS commit data to disk.

ikegami
  • 367,544
  • 15
  • 269
  • 518
2

How about this?

use strict;
use warnings;

use IO::Handle     qw( );  # For autoflush.
use Symbol         qw( gensym );
use Win32API::File qw( CloseHandle CreateFile GetOsFHandle OsFHandleOpen GENERIC_WRITE OPEN_ALWAYS FILE_FLAG_WRITE_THROUGH );
use Win32::API     qw( );

use constant WIN32API_FILE_NULL => [];

sub open_log_handle {
    my ($qfn) = @_;

    my $handle;
    if (!($handle = CreateFile(
        $qfn,
        GENERIC_WRITE,
        0,                        # Exclusive lock.
        WIN32API_FILE_NULL,       # No security descriptor.
        OPEN_ALWAYS,              # Create if doesn't exist.
        FILE_FLAG_WRITE_THROUGH,  # Flush writes immediately.
        WIN32API_FILE_NULL,       # No prototype.
    ))) {
        return undef;
    }

    my $fh = gensym();
    if (!OsFHandleOpen($fh, $handle, 'wa')) {
        my $e = $^E;
        CloseHandle($handle);
        $^E = $e;
        return undef;
    }

    $fh->autoflush(1);

    return $fh;
}

sub close_log_handle {
    my ($fh) = @_;

    my $handle = GetOsFHandle($fh)
        or return undef;

    if (!FlushFileBuffers($handle)) {
        my $e = $^E;
        close($fh);
        $^E = $e;
        return undef;
    }

    return close($fh);
}

my $FlushFileBuffers = Win32::API->new('kernel32.dll', 'FlushFileBuffers', 'N', 'N')
    or die $^E;

sub FlushFileBuffers {
    my ($handle) = @_;
    return $FlushFileBuffers->Call($handle);
}

{
    my $fh = open_log_handle('log.txt')
        or die $^E;

    print($fh "log!\n")
        or die $^E;

    close_log_handle($fh)
        or die $^E;
}
ikegami
  • 367,544
  • 15
  • 269
  • 518
0

The best you can do is sysopen with the O_SYNC fcntl flag, or fsync() from File::Sync; the options you were given insure that data aren't buffered inside your program but do nothing about whether the kernel is buffering writes (which it does because constantly flushing the same block to disk slows down all other I/O). And even then you might lose, because some hard drives will lie to the OS and claim that data has been committed to media when it's actually still in an on-drive memory buffer.

geekosaur
  • 59,309
  • 11
  • 123
  • 114
  • It doesn't seem possible to use File::Sync on Windows (except cygwin) – ikegami Mar 09 '11 at 07:43
  • I'm not sufficiently familiar with Windows programming to help; hopefully someone else can jump in. – geekosaur Mar 09 '11 at 07:45
  • O_SYNC is also not available in Windows! – Santhosh Mar 09 '11 at 09:20
  • 1
    I suggest you start a new thread to ask how to force Windows to commit a file to disk, dropping the Perl aspect. Once you figure how to do it at the API level, I can help you write a Win32::API wrapper to access the API. If you need my assistance with Win32::API, posting your request at www.perlmonks.org would be better since I would likely to miss the question here on StackOverfloww. – ikegami Mar 09 '11 at 19:58
  • @ikegami, thanks for the response, I have added an EDIT to the original question with how to perform unbuffered IO using Win32 APIs. Would be glad if you could help with the Win32 API wrapper. – Santhosh Mar 10 '11 at 12:54
  • I'll give it a stab later today. Win32API::File provides CreateFile, but might not be able to use it due to the buffer alignment issues. I'm hoping an unbuffered Perl print will do the trick for WriteFile, which will mean minimal intrusion into existing code. – ikegami Mar 10 '11 at 20:44
  • I did a bit of reading. `FILE_FLAG_NO_BUFFERING` would be very disruptive to existing code, and require a lot of work to use. To append to an existing file, one would need to read a bit of the existing file into memory, add to it, and write it back out. `FILE_FLAG_WRITE_THROUGH` is actually the desired behaviour, and I think it doesn't suffer from the alignment problems of `FILE_FLAG_NO_BUFFERING`. – ikegami Mar 10 '11 at 22:15