Git.pm: Add faculties to allow temp files to be cached
This patch offers a generic interface to allow temp files to be cached while using an instance of the 'Git' package. If many temp files are created and destroyed during the execution of a program, this caching mechanism can help reduce the amount of files created and destroyed by the filesystem. The temp_acquire method provides a weak guarantee that a temp file will not be stolen by subsequent requests. If a file is locked when another acquire request is made, a simple error is thrown. Signed-off-by: Marcus Griep <marcus@griep.us> Acked-by: Eric Wong <normalperson@yhbt.net>
Marcus Griep committed
Aug 12, 2008 at 12:00 UTC
e41352b24e29eba43d00a3fd117befaef1d594bc
1 file changed
+123
-2
perl/Git.pm
+123
-2
@@ -57,7 +57,8 @@ require Exporter;
57
command_output_pipe command_input_pipe command_close_pipe
58
command_bidi_pipe command_close_bidi_pipe
59
version exec_path hash_object git_cmd_try
60
- remote_refs);
60
+ remote_refs
61
+ temp_acquire temp_release temp_reset);
62
63
64
=head1 DESCRIPTION
@@ -99,7 +100,9 @@ use Carp qw(carp croak); # but croak is bad - throw instead
100
use Error qw(:try);
101
use Cwd qw(abs_path);
102
use IPC::Open2 qw(open2);
102
-
103
+use File::Temp ();
104
+require File::Spec;
105
+use Fcntl qw(SEEK_SET SEEK_CUR);
106
}
107
108
@@ -933,6 +936,124 @@ sub _close_cat_blob {
936
delete @$self{@vars};
937
}
938
939
+
940
+{ # %TEMP_* Lexical Context
941
+
942
+my (%TEMP_LOCKS, %TEMP_FILES);
943
+
944
+=item temp_acquire ( NAME )
945
+
946
+Attempts to retreive the temporary file mapped to the string C<NAME>. If an
947
+associated temp file has not been created this session or was closed, it is
948
+created, cached, and set for autoflush and binmode.
949
+
950
+Internally locks the file mapped to C<NAME>. This lock must be released with
951
+C<temp_release()> when the temp file is no longer needed. Subsequent attempts
952
+to retrieve temporary files mapped to the same C<NAME> while still locked will
953
+cause an error. This locking mechanism provides a weak guarantee and is not
954
+threadsafe. It does provide some error checking to help prevent temp file refs
955
+writing over one another.
956
+
957
+In general, the L<File::Handle> returned should not be closed by consumers as
958
+it defeats the purpose of this caching mechanism. If you need to close the temp
959
+file handle, then you should use L<File::Temp> or another temp file faculty
960
+directly. If a handle is closed and then requested again, then a warning will
961
+issue.
962
+
963
+=cut
964
+
965
+sub temp_acquire {
966
+ my ($self, $name) = _maybe_self(@_);
967
+
968
+ my $temp_fd = _temp_cache($name);
969
+
970
+ $TEMP_LOCKS{$temp_fd} = 1;
971
+ $temp_fd;
972
+}
973
+
974
+=item temp_release ( NAME )
975
+
976
+=item temp_release ( FILEHANDLE )
977
+
978
+Releases a lock acquired through C<temp_acquire()>. Can be called either with
979
+the C<NAME> mapping used when acquiring the temp file or with the C<FILEHANDLE>
980
+referencing a locked temp file.
981
+
982
+Warns if an attempt is made to release a file that is not locked.
983
+
984
+The temp file will be truncated before being released. This can help to reduce
985
+disk I/O where the system is smart enough to detect the truncation while data
986
+is in the output buffers. Beware that after the temp file is released and
987
+truncated, any operations on that file may fail miserably until it is
988
+re-acquired. All contents are lost between each release and acquire mapped to
989
+the same string.
990
+
991
+=cut
992
+
993
+sub temp_release {
994
+ my ($self, $temp_fd, $trunc) = _maybe_self(@_);
995
+
996
+ if (ref($temp_fd) ne 'File::Temp') {
997
+ $temp_fd = $TEMP_FILES{$temp_fd};
998
+ }
999
+ unless ($TEMP_LOCKS{$temp_fd}) {
1000
+ carp "Attempt to release temp file '",
1001
+ $temp_fd, "' that has not been locked";
1002
+ }
1003
+ temp_reset($temp_fd) if $trunc and $temp_fd->opened;
1004
+
1005
+ $TEMP_LOCKS{$temp_fd} = 0;
1006
+ undef;
1007
+}
1008
+
1009
+sub _temp_cache {
1010
+ my ($name) = @_;
1011
+
1012
+ my $temp_fd = \$TEMP_FILES{$name};
1013
+ if (defined $$temp_fd and $$temp_fd->opened) {
1014
+ if ($TEMP_LOCKS{$$temp_fd}) {
1015
+ throw Error::Simple("Temp file with moniker '",
1016
+ $name, "' already in use");
1017
+ }
1018
+ } else {
1019
+ if (defined $$temp_fd) {
1020
+ # then we're here because of a closed handle.
1021
+ carp "Temp file '", $name,
1022
+ "' was closed. Opening replacement.";
1023
+ }
1024
+ $$temp_fd = File::Temp->new(
1025
+ TEMPLATE => 'Git_XXXXXX',
1026
+ DIR => File::Spec->tmpdir
1027
+ ) or throw Error::Simple("couldn't open new temp file");
1028
+ $$temp_fd->autoflush;
1029
+ binmode $$temp_fd;
1030
+ }
1031
+ $$temp_fd;
1032
+}
1033
+
1034
+=item temp_reset ( FILEHANDLE )
1035
+
1036
+Truncates and resets the position of the C<FILEHANDLE>.
1037
+
1038
+=cut
1039
+
1040
+sub temp_reset {
1041
+ my ($self, $temp_fd) = _maybe_self(@_);
1042
+
1043
+ truncate $temp_fd, 0
1044
+ or throw Error::Simple("couldn't truncate file");
1045
+ sysseek($temp_fd, 0, SEEK_SET) and seek($temp_fd, 0, SEEK_SET)
1046
+ or throw Error::Simple("couldn't seek to beginning of file");
1047
+ sysseek($temp_fd, 0, SEEK_CUR) == 0 and tell($temp_fd) == 0
1048
+ or throw Error::Simple("expected file position to be reset");
1049
+}
1050
+
1051
+sub END {
1052
+ unlink values %TEMP_FILES if %TEMP_FILES;
1053
+}
1054
+
1055
+} # %TEMP_* Lexical Context
1056
+
1057
=back
1058
1059
=head1 ERROR HANDLING