File-Flock-2013.11000755001750001750 012215751222 12706 5ustar00muirmuir000000000000File-Flock-2013.11/Changes000444001750001750 462112215751222 14341 0ustar00muirmuir000000000000Revision history for Perl module File::Flock 2013.10 2013-09-16 - Switched from CHANGELOG to Changes 2013.09 2013-09-11 - Resolve un-initialized variable $ready in Subprocess.pm - Resolve un-initialized variable isues in Forking.pm 2013.08 2013-04-17 - Removed "my $_" instances that broke older perls. 2013.07 2013-04-09 - Require IO::Event version 0.812 to work around a FreeBSD issue. 2013.06 2013-04-05 - Added File::Flock::Forking to auto-select between File::Flock and File::Flock::Subprocess. - Added File::Flock::Subprocess for machines that don't propogate locks across fork(). - POD is now after __END__ instead of __DATA__. Oops! 2008.01 2008-03-27 - Joshua Kronengold, mneme at io dot com, sent in a patch to use IO::File instead of the $gensym hack. Applied. - Carl Fürstenber, azatoth at gmail dot com and others requested that license terms be spelled out. Done. 104.111901 2004-11-19 - Bugfix in &unlock for if the lock file has been removed. - Bugfix by Vadim O. Ustiansky . 101.060501 2001-06-05 - Added $av0debug variable to note locking attempts in $0 [2001-05-18] - Added lock_rename to the EXPORT list. 100.092501 2000-09-25 - Added tests to make sure 'nonblocking' works 99.121701 1999-12-17 - Added the lock_rename() function. 99.062201 1999-06-22 - SunOS systems seem to fail with EWOULDBLOCK on locked files. [1999-06-21] - It appears that on some systems (HP-UX) a blocking call to flock() can fail with EACCES instead of EAGAIN. [1999-06-15] - Perl changes. File::Flock must change to keep up. A call to lock() had to be changed to &lock(). Why? 98.120101 1998-12-01 - More fixes for Solaris. - Modified the unlock() function so that it can be called as a reference. 98.113001 1998-11-30 - Fixed the object-style interface. - Attempt to fix a double-unlock bug that makes the Linux port unhappy 98.112801 1998-11-26 - Chaged O_RDONLY to O_RDWR for all file opens because Solaris won't let you get an exclusive lock on a read-only file. Crazy! Change suggested by Lupe Christoph . Thanks! - Rewrote the handling of the removal of files created just so that they could be locked. Also tried to make sure that now file descriptors could get leaked. File-Flock-2013.11/README000444001750001750 351612215751222 13730 0ustar00muirmuir000000000000NAME File::Flock - file locking with flock SYNOPSIS use File::Flock; lock($filename); lock($filename, 'shared'); lock($filename, undef, 'nonblocking'); lock($filename, 'shared', 'nonblocking'); unlock($filename); lock_rename($oldfilename, $newfilename) my $lock = new File::Flock '/somefile'; $lock->unlock(); $lock->lock_rename('/new/file'); forget_locks(); DESCRIPTION Lock files using the flock() call. If the file to be locked does not exist, then the file is created. If the file was created then it will be removed when it is unlocked assuming it's still an empty file. Locks can be created by new'ing a File::Flock object. Such locks are automatically removed when the object goes out of scope. The unlock() method may also be used. lock_rename() is used to tell File::Flock when a file has been renamed (and thus the internal locking data that is stored based on the filename should be moved to a new name). unlock() the new name rather than the original name. Locks are released on process exit when the process that created the lock exits. Subprocesses that exit do not remove locks. Use forget_locks() or POSIX::_exit() to prevent unlocking on process exit. SEE ALSO See File::Flock::Subprocess for a variant that uses a subproess to hold the locks so that the locks survive when the parent process forks. See File::Flock::Forking for a way to automatically choose between File::Flock and File::Flock::Subprocess. LICENSE Copyright (C) 1996-2012 David Muir Sharnoff Copyright (C) 2013 Google, Inc. This module may be used/copied/etc on the same terms as Perl itself. PACKAGERS File::Flock is packaged for Fedora by Emmanuel Seyman . File-Flock-2013.11/META.yml000444001750001750 163212215751222 14316 0ustar00muirmuir000000000000--- abstract: 'Wrapper for flock() to make file locking trivial' author: - 'David Muir Sharnoff ' build_requires: File::Slurp: 0 Test::SharedFork: 0 Time::HiRes: 0 configure_requires: Module::Build: 0.36 generated_by: 'Module::Build version 0.3603' license: unknown meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: File-Flock provides: File::Flock: file: lib/File/Flock.pm version: 2013.11 File::Flock::Forking: file: lib/File/Flock/Forking.pm File::Flock::Subprocess: file: lib/File/Flock/Subprocess.pm File::Flock::Subprocess::Connections: file: lib/File/Flock/Subprocess.pm File::Flock::Subprocess::Master: file: lib/File/Flock/Subprocess.pm requires: AnyEvent: 0 Data::Structure::Util: 0 IO::Event: 0.812 Time::HiRes: 0 resources: repository: http://github.com/muir/File-Flock version: 2013.11 File-Flock-2013.11/MYMETA.json000444001750001750 325412215751222 14736 0ustar00muirmuir000000000000{ "abstract" : "Wrapper for flock() to make file locking trivial", "author" : [ "David Muir Sharnoff " ], "dynamic_config" : 0, "generated_by" : "Module::Build version 0.3603, CPAN::Meta::Converter version 2.112150", "license" : [ "unknown" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "File-Flock", "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : 0 } }, "configure" : { "requires" : { "Module::Build" : "0.36" } }, "runtime" : { "requires" : { "AnyEvent" : 0, "Data::Structure::Util" : 0, "File::Slurp" : 0, "IO::Event" : "0.812", "Test::SharedFork" : 0, "Time::HiRes" : 0 } } }, "provides" : { "File::Flock" : { "file" : "lib/File/Flock.pm", "version" : "2013.1" }, "File::Flock::Forking" : { "file" : "lib/File/Flock/Forking.pm", "version" : 0 }, "File::Flock::Subprocess" : { "file" : "lib/File/Flock/Subprocess.pm", "version" : 0 }, "File::Flock::Subprocess::Connections" : { "file" : "lib/File/Flock/Subprocess.pm", "version" : 0 }, "File::Flock::Subprocess::Master" : { "file" : "lib/File/Flock/Subprocess.pm", "version" : 0 } }, "release_status" : "stable", "resources" : { "repository" : { "url" : "http://github.com/muir/File-Flock" } }, "version" : "2013.1" } File-Flock-2013.11/Build.PL000444001750001750 161012215751222 14335 0ustar00muirmuir000000000000# Note: this file has been initially generated by Module::Build::Convert 0.49 use strict; use warnings; use Module::Build; my $build = Module::Build->new ( module_name => 'File::Flock', dist_abstract => 'Wrapper for flock() to make file locking trivial', dist_author => 'David Muir Sharnoff ', dist_version_from => 'lib/File/Flock.pm', requires => { 'AnyEvent' => 0, 'Data::Structure::Util' => 0, 'IO::Event' => '0.812', 'Time::HiRes' => 0, }, build_requires => { 'File::Slurp' => 0, 'Time::HiRes' => 0, 'Test::SharedFork' => 0, }, meta_merge => { resources => { repository => 'http://github.com/muir/File-Flock', }, }, license => 'unknown', create_readme => 1, create_makefile_pl => 'traditional', ); $build->create_build_script; File-Flock-2013.11/MYMETA.yml000444001750001750 165412215751222 14570 0ustar00muirmuir000000000000--- abstract: 'Wrapper for flock() to make file locking trivial' author: - 'David Muir Sharnoff ' build_requires: File::Slurp: 0 Test::SharedFork: 0 Time::HiRes: 0 configure_requires: Module::Build: 0.36 dynamic_config: 0 generated_by: 'Module::Build version 0.3603' license: unknown meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: File-Flock provides: File::Flock: file: lib/File/Flock.pm version: 2013.11 File::Flock::Forking: file: lib/File/Flock/Forking.pm File::Flock::Subprocess: file: lib/File/Flock/Subprocess.pm File::Flock::Subprocess::Connections: file: lib/File/Flock/Subprocess.pm File::Flock::Subprocess::Master: file: lib/File/Flock/Subprocess.pm requires: AnyEvent: 0 Data::Structure::Util: 0 IO::Event: 0.812 Time::HiRes: 0 resources: repository: http://github.com/muir/File-Flock version: 2013.10 File-Flock-2013.11/MANIFEST000444001750001750 42712215751222 14157 0ustar00muirmuir000000000000Build.PL Changes lib/File/Flock.pm lib/File/Flock/Forking.pm lib/File/Flock/Subprocess.pm MANIFEST MYMETA.yml MYMETA.json README t/auto.t t/auto2.t t/flock.t t/flock.tt t/flock2.t t/flock2.tt t/forking.t t/forking2.t t/subprocess.t t/subprocess2.t t/wrap.tm META.yml Makefile.PL File-Flock-2013.11/Makefile.PL000444001750001750 122112215751222 15011 0ustar00muirmuir000000000000# Note: this file was auto-generated by Module::Build::Compat version 0.3603 use ExtUtils::MakeMaker; WriteMakefile ( 'NAME' => 'File::Flock', 'VERSION_FROM' => 'lib/File/Flock.pm', 'PREREQ_PM' => { 'AnyEvent' => 0, 'Data::Structure::Util' => 0, 'File::Slurp' => 0, 'IO::Event' => '0.812', 'Test::SharedFork' => 0, 'Time::HiRes' => 0 }, 'INSTALLDIRS' => 'site', 'EXE_FILES' => [], 'PL_FILES' => {} ) ; File-Flock-2013.11/lib000755001750001750 012215751222 13454 5ustar00muirmuir000000000000File-Flock-2013.11/lib/File000755001750001750 012215751222 14333 5ustar00muirmuir000000000000File-Flock-2013.11/lib/File/Flock.pm000444001750001750 2026112215751222 16105 0ustar00muirmuir000000000000 package File::Flock; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(lock unlock lock_rename forget_locks); use Carp; use POSIX qw(EAGAIN EACCES EWOULDBLOCK ENOENT EEXIST O_EXCL O_CREAT O_RDWR); use Fcntl qw(LOCK_SH LOCK_EX LOCK_NB LOCK_UN); use IO::File; use Data::Structure::Util qw(unbless); use vars qw($VERSION $debug $av0debug); BEGIN { $VERSION = 2013.11; $debug = 0; $av0debug = 0; } use strict; no strict qw(refs); my %locks; # did we create the file? my %lockHandle; my %shared; my %pid; my %rm; sub new_flock { my ($pkg, $file, $shared, $nonblocking) = @_; lock_flock($file, $shared, $nonblocking) or return undef; return bless [$file], $pkg; } sub DESTROY { my ($this) = @_; unlock_flock($this->[0]); } sub lock_flock { my ($file, $shared, $nonblocking) = @_; my $f = new IO::File; my $created = 0; my $previous = exists $locks{$file}; # the file may be springing in and out of existance... OPEN: for(;;) { if (-e $file) { unless (sysopen($f, $file, O_RDWR)) { redo OPEN if $! == ENOENT; croak "open $file: $!"; } } else { unless (sysopen($f, $file, O_CREAT|O_EXCL|O_RDWR)) { redo OPEN if $! == EEXIST; croak "open >$file: $!"; } print STDERR " {$$ " if $debug; # } $created = 1; } last; } $locks{$file} = $created || $locks{$file} || 0; $shared{$file} = $shared; $pid{$file} = $$; $lockHandle{$file} = $f; my $flags; $flags = $shared ? LOCK_SH : LOCK_EX; $flags |= LOCK_NB if $nonblocking; local($0) = "$0 - locking $file" if $av0debug && ! $nonblocking; my $r = flock($f, $flags); print STDERR " ($$ " if $debug and $r; if ($r) { # let's check to make sure the file wasn't # removed on us! my $ifile = (stat($file))[1]; my $ihandle; eval { $ihandle = (stat($f))[1] }; croak $@ if $@; return 1 if defined $ifile and defined $ihandle and $ifile == $ihandle; # oh well, try again flock($f, LOCK_UN); close($f); return lock_flock($file); } return 1 if $r; if ($nonblocking and (($! == EAGAIN) or ($! == EACCES) or ($! == EWOULDBLOCK))) { if (! $previous) { delete $locks{$file}; delete $lockHandle{$file}; delete $shared{$file}; delete $pid{$file}; } if ($created) { # oops, a bad thing just happened. # We don't want to block, but we made the file. &background_remove($f, $file); } close($f); return 0; } croak "flock $f $flags: $!"; } # # get a lock on a file and remove it if it's empty. This is to # remove files that were created just so that they could be locked. # # To do this without blocking, defer any files that are locked to the # the END block. # sub background_remove { my ($f, $file) = @_; if (flock($f, LOCK_EX|LOCK_NB)) { unlink($file) if -s $file == 0; flock($f, LOCK_UN); return 1; } else { $rm{$file} = 1 unless exists $rm{$file}; return 0; } } sub unlock_flock { my ($file) = @_; if (ref $file eq 'File::Flock') { unbless $file; # avoid destructor later $file = $file->[0]; } croak "no lock on $file" unless exists $locks{$file}; my $created = $locks{$file}; my $unlocked = 0; my $size = -s $file; if ($created && defined($size) && $size == 0) { if ($shared{$file}) { $unlocked = &background_remove($lockHandle{$file}, $file); } else { # { print STDERR " $$} " if $debug; unlink($file) or croak "unlink $file: $!"; } } delete $locks{$file}; delete $pid{$file}; my $f = $lockHandle{$file}; delete $lockHandle{$file}; return 0 unless defined $f; print STDERR " $$) " if $debug; $unlocked or flock($f, LOCK_UN) or croak "flock $file UN: $!"; close($f); return 1; } sub lock_rename_flock { croak "arguments to lock_rename" unless @_ == 2; my ($oldfile, $newfile) = @_; if (ref $oldfile eq 'File::Flock') { my $obj = $oldfile; $oldfile = $obj->[0]; $obj->[0] = $newfile; } if (exists $locks{$newfile}) { unlock_flock($newfile); } delete $locks{$newfile}; delete $shared{$newfile}; delete $pid{$newfile}; delete $lockHandle{$newfile}; delete $rm{$newfile}; $locks{$newfile} = $locks{$oldfile} if exists $locks{$oldfile}; $shared{$newfile} = $shared{$oldfile} if exists $shared{$oldfile}; $pid{$newfile} = $pid{$oldfile} if exists $pid{$oldfile}; $lockHandle{$newfile} = $lockHandle{$oldfile} if exists $lockHandle{$oldfile}; $rm{$newfile} = $rm{$oldfile} if exists $rm{$oldfile}; delete $locks{$oldfile}; delete $shared{$oldfile}; delete $pid{$oldfile}; delete $lockHandle{$oldfile}; delete $rm{$oldfile}; return 1; } sub forget_locks_flock { %locks = (); %shared = (); %pid = (); %lockHandle = (); %rm = (); } # # Unlock any files that are still locked and remove any files # that were created just so that they could be locked. # sub final_cleanup_flock { my $f; for $f (keys %locks) { unlock_flock($f) if $pid{$f} == $$; } my %bgrm; for my $file (keys %rm) { my $f = new IO::File; if (sysopen($f, $file, O_RDWR)) { if (flock($f, LOCK_EX|LOCK_NB)) { unlink($file) if -s $file == 0; flock($f, LOCK_UN); } else { $bgrm{$file} = 1; } close($f); } } if (%bgrm) { my $ppid = fork; croak "cannot fork" unless defined $ppid; my $pppid = $$; my $b0 = $0; $0 = "$b0: waiting for child ($ppid) to fork()"; unless ($ppid) { my $pid = fork; croak "cannot fork" unless defined $pid; unless ($pid) { for my $file (keys %bgrm) { my $f = new IO::File; if (sysopen($f, $file, O_RDWR)) { if (flock($f, LOCK_EX)) { unlink($file) if -s $file == 0; flock($f, LOCK_UN); } close($f); } } print STDERR " $pppid] $pppid)" if $debug; } kill(9, $$); # exit w/o END or anything else } waitpid($ppid, 0); kill(9, $$); # exit w/o END or anything else } %locks = (); %lockHandle = (); %shared = (); %pid = (); %rm = (); %bgrm = (); } END { final_cleanup(); } BEGIN { if ($File::Flock::Forking::SubprocessEnabled) { require File::Flock::Subprocess; *new = *File::Flock::Subprocess::new; *final_cleanup = *File::Flock::Subprocess::final_cleanup; *lock = *File::Flock::Subprocess::lock; *unlock = *File::Flock::Subprocess::unlock; *lock_rename = *File::Flock::Subprocess::lock_rename; *forget_locks = *File::Flock::Subprocess::forget_locks; } else { *new = *new_flock; *final_cleanup = *final_cleanup_flock; *lock = *lock_flock; *unlock = *unlock_flock; *lock_rename = *lock_rename_flock; *forget_locks = *forget_locks_flock; } } 1; __END__ =head1 NAME File::Flock - file locking with flock =head1 SYNOPSIS use File::Flock; lock($filename); lock($filename, 'shared'); lock($filename, undef, 'nonblocking'); lock($filename, 'shared', 'nonblocking'); unlock($filename); lock_rename($oldfilename, $newfilename) my $lock = new File::Flock '/somefile'; $lock->unlock(); $lock->lock_rename('/new/file'); forget_locks(); =head1 DESCRIPTION Lock files using the flock() call. If the file to be locked does not exist, then the file is created. If the file was created then it will be removed when it is unlocked assuming it's still an empty file. Locks can be created by new'ing a B object. Such locks are automatically removed when the object goes out of scope. The B method may also be used. B is used to tell File::Flock when a file has been renamed (and thus the internal locking data that is stored based on the filename should be moved to a new name). B the new name rather than the original name. Locks are released on process exit when the process that created the lock exits. Subprocesses that exit do not remove locks. Use forget_locks() or POSIX::_exit() to prevent unlocking on process exit. =head1 SEE ALSO See L for a variant that uses a subproess to hold the locks so that the locks survive when the parent process forks. See L for a way to automatically choose between File::Flock and L. =head1 LICENSE Copyright (C) 1996-2012 David Muir Sharnoff Copyright (C) 2013 Google, Inc. This module may be used/copied/etc on the same terms as Perl itself. =head1 PACKAGERS File::Flock is packaged for Fedora by Emmanuel Seyman . File-Flock-2013.11/lib/File/Flock000755001750001750 012215751222 15371 5ustar00muirmuir000000000000File-Flock-2013.11/lib/File/Flock/Forking.pm000444001750001750 263012215751222 17464 0ustar00muirmuir000000000000 package File::Flock::Forking; require Exporter; @ISA = qw(Exporter); use strict; use Config; die "Import File::Flock::Forking before importing File::Flock" if defined $File::Flock::VERSION; if ((!$Config{d_flock} && ! ($ENV{FLOCK_FORKING_USE} || '') eq 'flock') || (($ENV{FLOCK_FORKING_USE} || '') eq 'subprocess')) { $File::Flock::Forking::SubprocessEnabled = 1; require File::Flock::Subprocess; } 1; __END__ =head1 NAME File::Flock::Forking - adjust File::Flock to handle fork() =head1 SYNOPSIS use File::Flock::Forking; use File::Flock; =head1 DESCRIPTION The purpose of File::Flock::Forking is to change the implementation of L to handle locking on systems that do not hold locks across calls to fork(). If you are using L or any module that uses L then and your program uses fork(), then you should import File::Flock::Forking before you import L or any module that uses L. On most operating systems, File::Flock::Forking does nothing. On Solaris, it changes the behavior of L to be implemented by L. You can also force it to use L by with $ENV{FLOCK_FORKING_USE} = 'subprocess' Or force it to use L with $ENV{FLOCK_FORKING_USE} = 'flock' =head1 LICENSE Copyright (C) 2013 Google, Inc. This module may be used/copied/etc on the same terms as Perl itself. File-Flock-2013.11/lib/File/Flock/Subprocess.pm000444001750001750 2755012215751222 20245 0ustar00muirmuir000000000000 package File::Flock::Subprocess; @ISA = qw(Exporter); @EXPORT = qw(lock unlock lock_rename forget_lock); # use Smart::Comments; use strict; use warnings; require Exporter; require POSIX; use Socket; use IO::Handle; use Time::HiRes qw(sleep time); use Carp; use File::Temp qw(tempdir); use IO::Socket::UNIX; use Data::Structure::Util qw(unbless); # shared my $dir; my $socket; my $av0; my $debug; BEGIN { $debug = 0; } # proxy server my $connections; my $parent_pid; my $timer; my $ioe_parent; my $counter = '0001'; my %locks; # client side my $child; my %lock_pids; # filename -> pid my %lock_proxies; # pid -> proxy my %lock_count; # pid -> count my $last_pid; sub new { my ($pkg, $file, $shared, $nonblocking) = @_; &lock($file, $shared, $nonblocking) or return undef; return bless [$file], __PACKAGE__; } sub DESTROY { my ($this) = @_; unlock($this->[0]); } sub encode { local($_); for $_ (@_) { ### assert: defined $_ s/\\/\\\\/g; s/\n/\\n/g; s/\t/\\t/g; } } sub decode { local($_); for $_ (@_) { ### assert: defined $_ s/\\t/\t/g; s/\\n/\n/g; s/\\\\/\\/g; } } sub update_proxy_connections { use Carp qw(longmess); print STDERR longmess("last_pid undefined") unless defined $last_pid; return if $last_pid == $$; ### UPDATING PROXY CONNECTIONS: "$$ IS NOT $last_pid" $last_pid = $$; for my $pid (keys %lock_proxies) { my $proxy = IO::Socket::UNIX->new( Peer => "$socket.$pid", Type => SOCK_STREAM, ) or carp "Could not open connection to lockserver $socket.$pid: $!"; ### CLOSING OLD $$ $lock_proxies{$pid}->close(); $lock_proxies{$pid} = $proxy; } ### DONE UPDATING: $$ } sub request { my ($request, $file) = @_; my $av0 = $0; local($0) = $av0; $0 = "$av0 - lock proxy request $request"; my $ts_before = time; ### REQUEST: "$$ $request" my $proxy = $lock_proxies{$lock_pids{$file}} or die; $proxy->print("$$ $request\n") or croak "print to lock proxy: $!"; for(;;) { my $ok = $proxy->getline(); chomp($ok); ### RESPONSE: $ok if ($ts_before) { my $diff = time - $ts_before; } if ($ok =~ /^ERROR:(.*)/) { my $error = $1; decode($error); ### ................. $error $error =~ s/\n.*//s; ### ..... $error croak $error; } elsif ($ok =~ /^RESULT=(\d+)/) { ### RESULT: $$.$1 return $1; } else { die "unexpected response from lock proxy: $ok"; } } } sub lock { my ($file, $shared, $nonblocking) = @_; update_proxy_connections(); if (!$lock_pids{$file}) { $lock_pids{$file} = $$; $lock_count{$$}++; } if (!$lock_proxies{$$}) { $lock_proxies{$$} = IO::Socket::UNIX->new( Peer => $socket, Type => SOCK_STREAM, ) or carp "Could not open connection to lockserver $socket: $!"; request("LISTEN", $file); } $shared = $shared ? "1" : "0"; $nonblocking = $nonblocking ? "1" : "0"; my $orig_file = $file; encode($file); my $r = request("LOCK $shared$nonblocking $file", $file); $locks{$orig_file} = $$ if $r; return $r; } sub unlock { my ($file) = @_; if (ref $file eq __PACKAGE__) { unbless $file; # avoid destructor later $file = $file->[0]; } update_proxy_connections(); if (ref $file eq 'File::Flock') { bless $file, 'UNIVERSAL'; # avoid destructor later $file = $$file; } croak "File $file not locked" unless $lock_pids{$file}; my $orig_file = $file; encode($file); my $r = request("UNLOCK $file", $file); my $lock_pid = delete $lock_pids{$orig_file}; if ($lock_count{$lock_pid} <= 0) { delete $lock_proxies{$lock_pid}; } delete $locks{$orig_file}; return $r; } sub lock_rename { croak "arguments to lock_rename" unless @_ == 2; my ($oldfile, $newfile) = @_; if (ref $oldfile eq 'File::Flock::Subprocess') { my $obj = $oldfile; $oldfile = $obj->[0]; $obj->[0] = $newfile; } update_proxy_connections(); carp "File $oldfile not locked" unless $lock_pids{$oldfile}; carp "File $newfile already locked" if $lock_pids{$newfile}; my ($orig_oldfile, $orig_newfile) = ($oldfile, $newfile); encode($oldfile, $newfile); my $r = request("LOCK_RENAME $oldfile\t$newfile", $oldfile); $lock_pids{$orig_newfile} = delete $lock_pids{$orig_oldfile}; $locks{$orig_newfile} = delete $locks{$orig_oldfile} if exists $locks{$orig_oldfile}; return $r; } sub forget_locks { %locks = (); } sub final_cleanup { for (keys %locks) { unlock($_) if $locks{$_} == $$; } $child->close() if defined $child; undef $child; undef %lock_proxies; } END { final_cleanup(); } sub run_lockserver { my ($parent) = @_; require IO::Event; import IO::Event 'AnyEvent'; my $ioe_listener = IO::Event::Socket::UNIX->new( Type => SOCK_STREAM, Local => $socket, Listen => 255, Handler => 'File::Flock::Subprocess::Master', Description => "listen($socket)", ); carp "could not listen on unix socket: $!" unless $ioe_listener; # we don't add a connection for the listener $parent->print("ready\n"); $ioe_parent = IO::Event->new($parent, __PACKAGE__, { description => 'socketpair', read_only => 1}); $connections->add($ioe_parent); if ($debug) { $timer = IO::Event->timer( interval => 2, cb => sub { $connections->display() }, ); } IO::Event::loop(); File::Flock::final_cleanup_flock(); } { package File::Flock::Subprocess::Master; use strict; use warnings; # lock proxy master accepting connection to start new child sub ie_connection { my ($pkg, $ioe) = @_; my $client = $ioe->accept('File::Flock::Subprocess') or die; ### CONNECT IN MASTER: "$$ - @{[$ioe->ie_desc()]}" my $new_child; for(;;) { $new_child = fork(); ### FORKED IN ACCEPT ### PID: $$ ### CHILD: $new_child last if defined $new_child; warn "Could not fork: $!"; sleep(1); } if ($new_child) { # now is as good a time as any to clean up zombies my $kid; do { $kid = waitpid(-1, &POSIX::WNOHANG); ### CHILD PROXY ZOMBIE REAPED: $kid } while $kid > 0; $client->close(); } else { $ioe->close(); $connections->remove($ioe_parent); $ioe_parent->close(); undef $ioe_parent; ### NEW CHILD PROXY SERVER $$ $av0 = "Locking proxy slave for $parent_pid using $socket"; $connections->add($client, "connection($socket)"); } } sub ie_input { die; } sub ie_eof { die; } } # lock proxy children accepting replacement connections sub ie_connection { my ($pkg, $ioe) = @_; my $replacement = $ioe->accept(); $connections->add($replacement, "slave(@{[$ioe->ie_desc().$counter++]})"); } # could be lock server master losing socketpair or lock server # proxy child losing a client sub ie_eof { ### EOF IN CHILD my ($handler, $ioe, $input_buffer_reference) = @_; $ioe->close(); unless ($connections->remove($ioe)) { ### "PROXY SERVER $$ ALL DONE" IO::Event::unloop_all(); } } sub ie_input { ### INPUT IN CHILD my ($handler, $ioe, $input_buffer_reference) = @_; $0 = "$av0: processing request"; while (my $request = $ioe->getline()) { $0 = "$av0: handling $request"; my $pid; $request =~ s/^(\d+) // or die "bad request to lock proxy: $request"; $pid = $1; $0 = "$av0: handling $request from $pid: $request"; my $r; ### PROCESSING REQUEST FROM $pid : $request eval { if ($request =~ m{^LOCK (.)(.) (.*)\n}s) { my ($shared, $nonblocking, $file) = ($1, $2, $3); decode($file); $r = File::Flock::lock_flock($file, $shared, $nonblocking); } elsif ($request =~ m{^UNLOCK (.*)\n}s) { my $file = $1; decode($file); $r = File::Flock::unlock_flock($file); } elsif ($request =~ m{^LOCK_RENAME (.*?)\t(.*)\n}) { my ($oldfile, $newfile) = ($1, $2); decode($oldfile, $newfile); $r = File::Flock::lock_rename_flock($oldfile, $newfile); } elsif ($request =~ m{^LISTEN\n}) { IO::Event::Socket::UNIX->new( Type => SOCK_STREAM, Local => "$socket.$pid", Listen => 255, Description => "slave($socket.$pid)", ) or die "Listen $socket.$pid: $!"; $r = 1; } elsif ($request =~ m{^QUIT\n}) { $r = 1; } else { die "Unknown remote lock request: $request"; } }; if ($@) { my $error = $@; encode($error); $ioe->print("ERROR:$error\n"); } else { $r = 0 + $r; $ioe->print("RESULT=$r\n"); } $0 = "$av0: idle"; } } { package File::Flock::Subprocess::Connections; use strict; use warnings; sub new { return bless {}; } sub add { my ($self, $ioe, $label) = @_; $ioe->ie_desc($label) if $label; die "duplicate @{[$ioe->ie_desc()]}" if ++$self->{$ioe->ie_desc()} > 1; print STDERR "PROXY $$: " . join(' ', 'ADD', $ioe->ie_desc(), ':', sort keys %$self) . "\n" if $debug; } sub remove { my ($self, $ioe) = @_; die $ioe unless $self->{$ioe->ie_desc()}; delete $self->{$ioe->ie_desc()};; print STDERR "PROXY $$: " . join(' ', 'REMOVE', $ioe->ie_desc(), ':', sort keys %$self) . "\n" if $debug; return scalar(keys %$self); } sub display { my ($self) = @_; print STDERR "PROXY $$: " . join(' ', sort keys %$self) . "\n" if $debug; } } BEGIN { # Let File::Flock know we're live with Subprocess $File::Flock::Forking::SubprocessEnabled = 1; require File::Flock; $dir = tempdir(CLEANUP => 0); $socket = "$dir/lock"; my $parent = new IO::Handle; $child = new IO::Handle; socketpair($parent, $child, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die "cannot create socketpair: $!"; $parent_pid = $$; my $child_pid; ### FORKING: $$ for(;;) { $child_pid = fork(); ### CHILD: $child_pid last if defined $child_pid; warn "Could not fork: $!"; sleep(1); } if ($child_pid) { $parent->close(); my $ready = <$child>; die unless $ready && $ready eq "ready\n"; $last_pid = $$; # We need File::Flock->new() to work. This is a bit gross: *File::Flock::new = \&File::Flock::Subprocess::new unless defined &File::Flock::new; if ($debug) { $SIG{ALRM} = sub { print STDERR "$$ Alive with " . ($child ? "child defined" : "child undefined") . "\n"; alarm(2); }; alarm(2); } } else { require IO::Event; $av0 = "Locking proxy master for $parent_pid, using $socket"; $0 = $av0; $child->close(); undef $child; $connections = File::Flock::Subprocess::Connections->new(); run_lockserver($parent); POSIX::_exit(0); die; } } 1; __END__ Implementation notes. We're trying to mimic the bahavior of locking on systems that preserve locks across fork(). We create connections to the proxy server as needed. When we make such connections, we record (with the connection) our current process PID. Whenever we have a new lock()/unlock()/lock_rename() request, we check to see if we're still the same process we used to be. If not, we re-open connections to the lock proxies. This way connections aren't shared with child processes. =head1 NAME File::Flock::Subprocess - file locking with flock in a subprocess =head1 SYNOPSIS use File::Flock::Subprocess; lock($filename); lock($filename, 'shared'); lock($filename, undef, 'nonblocking'); lock($filename, 'shared', 'nonblocking'); unlock($filename); lock_rename($oldfilename, $newfilename) my $lock = new File::Flock '/somefile'; $lock->unlock(); $lock->lock_rename('/new/file'); forget_locks(); =head1 DESCRIPTION This is a wrapper around L that starts a subprocess and does the lcoking in the subprocess with L. The purpose of this is to handle operating systems (eg: Solaris) that do not retain locks across a call to fork(). The sub-process for this is created with fork() when File::Flock::Subprocess is compiled. I've tried to minimize the side-effects calling fork() by doing calling it early and by using POSIX::_exit() to quit but it is still worth being aware of. I suggest loading File::Flock::Subprocess early. Use L to automatically detect when this is needed. Read the docs for L for details of the API. =head1 ERRATA Any errors reported by the locking proxy File::Flock::Subprocess starts will be reported as "Compilation Failed" errors because the proxy is started in a BEGIN{} block. =head1 LICENSE Copyright (C) 2013 Google, Inc. This module may be used/copied/etc on the same terms as Perl itself. File-Flock-2013.11/t000755001750001750 012215751222 13151 5ustar00muirmuir000000000000File-Flock-2013.11/t/subprocess2.t000444001750001750 27112215751222 15725 0ustar00muirmuir000000000000#!/usr/bin/perl use FindBin; require "$FindBin::Bin/wrap.tm"; dirwrap(sub { require File::Flock::Subprocess; import File::Flock::Subprocess; require "$FindBin::Bin/flock2.tt" }); File-Flock-2013.11/t/auto.t000444001750001750 53612215751222 14427 0ustar00muirmuir000000000000#!/usr/bin/perl BEGIN { use Config; if ($Config{d_flock}) { print "1..0 # Skipped: flock() is supported on this platform\n"; exit 0; } } use FindBin; require "$FindBin::Bin/wrap.tm"; dirwrap(sub { require File::Flock::Forking; import File::Flock::Forking; require File::Flock; import File::Flock; require "$FindBin::Bin/flock.tt" }); File-Flock-2013.11/t/forking.t000444001750001750 40612215751222 15112 0ustar00muirmuir000000000000#!/usr/bin/perl use FindBin; require "$FindBin::Bin/wrap.tm"; dirwrap(sub { $ENV{FLOCK_FORKING_USE} = 'subprocess'; require File::Flock::Forking; import File::Flock::Forking; require File::Flock; import File::Flock; require "$FindBin::Bin/flock.tt" }); File-Flock-2013.11/t/flock.t000555001750001750 51312215751222 14553 0ustar00muirmuir000000000000#!/usr/bin/perl BEGIN { use Config; if (!$Config{d_flock}) { print "1..0 # Skipped: flock() not supported on this platform, use File::Flock::Forking to workaround\n"; exit 0; } } use FindBin; require "$FindBin::Bin/wrap.tm"; dirwrap(sub { require File::Flock; import File::Flock; require "$FindBin::Bin/flock.tt"; }); File-Flock-2013.11/t/auto2.t000444001750001750 53712215751222 14512 0ustar00muirmuir000000000000#!/usr/bin/perl BEGIN { use Config; if ($Config{d_flock}) { print "1..0 # Skipped: flock() is supported on this platform\n"; exit 0; } } use FindBin; require "$FindBin::Bin/wrap.tm"; dirwrap(sub { require File::Flock::Forking; import File::Flock::Forking; require File::Flock; import File::Flock; require "$FindBin::Bin/flock2.tt" }); File-Flock-2013.11/t/flock2.tt000444001750001750 1073112215751222 15061 0ustar00muirmuir000000000000 use FindBin; require "$FindBin::Bin/wrap.tm"; use File::Slurp; use Time::HiRes qw(sleep); use POSIX qw(_exit); use File::Flock; use Test::More tests => 20; use Test::SharedFork; use strict; use warnings; test_lock_held_across_fork(); test_locks_dropped_on_sole_process_exit(); test_locks_dropped_on_multi_process_exit(); test_lock_rename_object(); test_forget_locks(); our $dir; # set in wrap.tt sub test_lock_held_across_fork { my $lock1 = "$dir/lhaf1"; my $lock2 = "$dir/lhaf2"; if (dofork()) { lock($lock1); my $l = File::Flock->new($lock2); write_file("$dir/gate1", ""); POSIX::_exit(0) unless dofork(); write_file("$dir/gate2", ""); sleep(0.1) while ! -e "$dir/gate3"; ok(! -e "$dir/gotlock1a", "lock held"); ok(! -e "$dir/gotlock1b", "obj lock held"); ok(! -e "$dir/gotlock2a", "child lock held"); ok(! -e "$dir/gotlock2b", "child obj lock held"); unlock($lock1); write_file("$dir/gate4", ""); sleep(0.1) while ! -e "$dir/gate5"; ok(-e "$dir/gotlock3a", "lock released"); ok(! -e "$dir/gotlock3b", "obj lock not released"); $l->unlock(); write_file("$dir/gate6", ""); sleep(0.1) while ! -e "$dir/gate7"; ok(-e "$dir/gotlock4", "obj lock released"); write_file("$dir/gate8", ""); } else { sleep(0.1) while ! -e "$dir/gate1"; # parent has locked lock write_file("$dir/gotlock1a", "") if lock($lock1, undef, 'nonblocking'); write_file("$dir/gotlock1b", "") if lock($lock2, undef, 'nonblocking'); sleep(0.1) while ! -e "$dir/gate2"; write_file("$dir/gotlock2a", "") if lock($lock1, undef, 'nonblocking'); write_file("$dir/gotlock2b", "") if lock($lock2, undef, 'nonblocking'); write_file("$dir/gate3", ""); sleep(0.1) while ! -e "$dir/gate4"; write_file("$dir/gotlock3a", "") if lock($lock1, undef, 'nonblocking'); write_file("$dir/gotlock3b", "") if lock($lock2, undef, 'nonblocking'); write_file("$dir/gate5", ""); sleep(0.1) while ! -e "$dir/gate6"; write_file("$dir/gotlock4", "") if lock($lock2, undef, 'nonblocking'); write_file("$dir/gate7", ""); sleep(0.1) while ! -e "$dir/gate8"; exit(0); } } sub test_locks_dropped_on_sole_process_exit { my $p = "$dir/tldospe"; my $pid; if (($pid = dofork())) { sleep(0.1) while ! -e "$p.gate1"; ok(! lock("$p.lock1", undef, 'nonblocking'), "can't get lock"); write_file("$p.gate2", ""); waitpid($pid, 0); ok(lock("$p.lock1", undef, 'nonblocking'), "can get lock"); } else { lock("$p.lock1"); write_file("$p.gate1", ""); sleep(0.1) while ! -e "$p.gate2"; exit(0); } } sub test_locks_dropped_on_multi_process_exit { my $p = "$dir/tldompe"; my $pid; if (($pid = dofork())) { sleep(0.1) while ! -e "$p.gate1"; ok(! lock("$p.lock1", undef, 'nonblocking'), "can't get lock"); write_file("$p.gate2", ""); waitpid($pid, 0); ok(lock("$p.lock1", undef, 'nonblocking'), "can get lock"); write_file("$p.gate3", ""); } else { lock("$p.lock1"); if (dofork()) { write_file("$p.gate1", ""); sleep(0.1) while ! -e "$p.gate2"; exit(0); } else { sleep(0.1) while ! -e "$p.gate3"; exit(0); } } } sub test_lock_rename_object { my $p = "$dir/tlro"; my $l = File::Flock->new("$p.oldlock"); undef $!; undef $@; ok(eval {rename("$p.oldlock", "$p.newlock")}, "rename file - $!"); ok(eval {$l->lock_rename("$p.newlock")}, "rename lock - $@"); ok(eval {$l->unlock()}, "unlock - $@"); } sub test_forget_locks { my $p = "$dir/tfl"; my $pid; if (($pid = dofork())) { sleep(0.1) while ! -e "$p.gate1"; ok(! lock("$p.lock1", undef, 'nonblocking'), "can't get multi lock"); write_file("$p.gate2", ""); # forget locks sleep(0.1) while ! -e "$p.gate4"; ok(! lock("$p.lock1", undef, 'nonblocking'), "still can't get multi lock"); write_file("$p.gate5", ""); # sub master quits waitpid($pid, 0); ok(kill(0, $pid) == 0, "first proc ($pid) is dead"); ok(! lock("$p.lock1", undef, 'nonblocking'), "and still can't get multi lock"); write_file("$p.gate3", ""); my $pid2 = read_file("$p.gate1"); sleep(0.1) while kill(0, $pid2); ok(kill(0, $pid2) == 0, "second proc ($pid2) is dead"); ok(lock("$p.lock1", undef, 'nonblocking'), "now can get multi lock"); } else { lock("$p.lock1"); my $subpid; if (($subpid = dofork())) { write_file("$p.gate1", "$subpid"); sleep(0.1) while ! -e "$p.gate2"; forget_locks(); write_file("$p.gate4", ""); sleep(0.1) while ! -e "$p.gate5"; exit(0); } else { sleep(0.1) while ! -e "$p.gate3"; exit(0); } } } sub dofork { my $p = fork(); die unless defined $p; return $p; } File-Flock-2013.11/t/forking2.t000444001750001750 40712215751222 15175 0ustar00muirmuir000000000000#!/usr/bin/perl use FindBin; require "$FindBin::Bin/wrap.tm"; dirwrap(sub { $ENV{FLOCK_FORKING_USE} = 'subprocess'; require File::Flock::Forking; import File::Flock::Forking; require File::Flock; import File::Flock; require "$FindBin::Bin/flock2.tt" }); File-Flock-2013.11/t/subprocess.t000444001750001750 27012215751222 15642 0ustar00muirmuir000000000000#!/usr/bin/perl use FindBin; require "$FindBin::Bin/wrap.tm"; dirwrap(sub { require File::Flock::Subprocess; import File::Flock::Subprocess; require "$FindBin::Bin/flock.tt" }); File-Flock-2013.11/t/flock2.t000555001750001750 51412215751222 14636 0ustar00muirmuir000000000000#!/usr/bin/perl BEGIN { use Config; if (!$Config{d_flock}) { print "1..0 # Skipped: flock() not supported on this platform, use File::Flock::Forking to workaround\n"; exit 0; } } use FindBin; require "$FindBin::Bin/wrap.tm"; dirwrap(sub { require File::Flock; import File::Flock; require "$FindBin::Bin/flock2.tt"; }); File-Flock-2013.11/t/wrap.tm000444001750001750 116012215751222 14617 0ustar00muirmuir000000000000 use File::Temp; use Data::Structure::Util qw(unbless); use IO::Socket::UNIX; require POSIX; use Socket; use IO::Handle; our $dir; sub dirwrap { my ($code) = @_; my $dirobj = File::Temp->newdir(); $dir = $dirobj->dirname(); my $parent = new IO::Handle; my $child = new IO::Handle; socketpair($parent, $child, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die "cannot create socketpair: $!"; my $pid = fork(); if ($pid) { unbless $dirobj; $parent->close(); $code->(); $child->close(); } elsif (defined $pid) { $child->close(); while(<$parent>) {}; } else { die "could not fork: $!"; } exit(0); } 1; File-Flock-2013.11/t/flock.tt000444001750001750 1230012215751222 14771 0ustar00muirmuir000000000000#!/usr/bin/perl use Carp; use FileHandle; use File::Slurp; use strict; use warnings; use Time::HiRes; our $dir; # set in wrap.tm die unless $dir; my $counter = "$dir/counter"; my $lock = "$dir/lock"; my $lock2 = "$dir/lock2"; my $lock3 = "$dir/lock3"; my $lock4 = "$dir/lock4"; my $lock5 = "$dir/lock5"; my $lock6 = "$dir/lock6"; my $lock7 = "$dir/lock7"; STDOUT->autoflush(1); my $children = 6; my $count = 120; die unless $count % 2 == 0; die unless $count % 3 == 0; print "1..".($count*1.5+$children*2+7)."\n"; my %locks; my $acquiring = ''; my $releasing = ''; my $parent; my $child = 0; my $i; for $i (1..$children) { my $p = fork(); croak unless defined $p; $parent = $p or $child = $i; last unless $parent; } my $pdesc = "process $$, " . ($parent ? "the parent" : "child # $child"); print "# $pdesc\n"; my $lastline; my $lastdebug = 0; $SIG{WINCH} = sub { if (time - $lastdebug > .5) { $lastdebug = time; debugprint(); } }; sub debugprint { print STDERR "# $pdesc at $lastline" . (scalar(keys %locks) ? " holding locks on " . join(' ', map { "$_$locks{$_}" } sort keys %locks) : '') . ($acquiring ? " trying to acquire lock on $acquiring" : "") . ($releasing ? " trying to release lock on $releasing" : "") . "\n"; } STDOUT->autoflush(1); sub dolock; sub dounlock; dp(); if ($parent) { print "ok 1\n"; &write_file($counter, "2"); &write_file($lock, ""); &write_file($lock4, ""); dolock($lock4); } else { my $e = 1; while (! -e $lock) { # spin print "# $pdesc spinning\n" if $e %2000 == 0; die if $e++ > 1000000; } dp(); dolock($lock3, 'shared'); } dp(); dolock($lock2, 'shared'); dp(); my $c; my $ee; while (($c = &read_file($counter)) < $count) { die if $ee++ > 10000000; if ($c < $count*.25 || $c > $count*.75) { dolock($lock); } else { dolock($lock, 0, 1) || next; } $c = &read_file($counter); # make sure each child increments it at least once. if ($c < $children+2 && $c != $child+2) { dounlock($lock); next; } if ($c < $count) { print "ok $c\n"; $c++; &overwrite_file($counter, "$c"); } # one of the children will exit (and thus need to clean up) if ($c == $count/3) { exit(0) if fork() == 0; } # deal with a missing lock file if ($c == $count/2) { unlink($lock) or croak "unlink $lock: $!"; } # make sure the lock file doesn't get deleted if ($c == int($count*.9)) { &overwrite_file($lock, "keepme"); } dounlock($lock); } dp(); dolock($lock); $c = &read_file($counter); print "ok $c\n"; $c++; &overwrite_file($counter, "$c"); dounlock($lock); dp(); if ($c == $count+$children+1) { print "ok $c\n"; $c++; if (&read_file($lock) eq 'keepme') {print "ok $c\n";} else {print "not ok $c\n"}; unlink($lock); $c++; } dounlock($lock2); if ($parent) { dolock($lock2); dounlock($lock2); $c = $count+$children+3; &write_file($counter, $c); dounlock($lock4); } # okay, now that that's all done, lets try some locks using # the object interface... my $start = $c; for(;;) { my $l = dolock2($lock4); $c = &read_file($counter); last if $c > $count/2+$start; print "ok $c\n"; $c++; &overwrite_file($counter, "$c"); } delete $locks{$lock4}; # unlocked by going out of scope # # now let's make sure nonblocking works # if ($parent) { my $e; dolock $lock6; for(;;) { dp(); dolock($lock7, undef, 'nonblocking') or last; dp(); dounlock($lock7); dp(); die if $e++ > 1000; sleep(1); } dp(); dounlock $lock6; dp(); dolock $counter; dp(); $c = &read_file($counter); print "ok $c\n"; $c++; &overwrite_file($counter, "$c"); dp(); dounlock $counter; dp(); } elsif ($child == 1) { dp(); my $e; for(;;) { dolock($lock6, undef, 'nonblocking') or last; dounlock($lock6); die if $e++ > 1000; sleep(1); } dolock $lock7; dolock $lock6; dolock $counter; $c = &read_file($counter); print "ok $c\n"; $c++; &overwrite_file($counter, "$c"); dounlock $counter; dounlock $lock7; dounlock $lock6; } dp(); # # Shut everything down # if ($parent) { dp(); my $l = new File::Flock $lock3; $c = &read_file($counter); if ($l) { print "ok $c\n" } else {print "not ok $c\n"} $c++; unlink($counter); unlink($lock4); unlink($lock); dolock($lock5); dounlock($lock5); if (-e $lock5) { print "not ok $c\n" } else {print "ok $c\n"} $c++; my $x = ''; for (1..$children) { dp(); wait(); dp(); my $status = $? >> 8; if ($status) { $x .= "not ok $c\n";} else {$x .= "ok $c\n"} $c++; } $releasing = $lock3; $l->unlock(); undef $releasing; delete $locks{$lock3}; print $x; dp(); } else { dp(); dounlock($lock3); } dp(); exit(0); sub dolock { $lastline = (caller())[2]; my $s = ""; $s .= ":" if ($_[1] || $_[2]); $s .= ":Shared" if $_[1]; $s .= ":Nonblocking" if $_[2]; my $r = lock(@_); $locks{$_[0]} = $s if $r; undef $acquiring; return $r; } sub dolock2 { $lastline = (caller())[2]; my $s = ""; $s .= ":" if ($_[1] || $_[2]); $s .= ":Shared" if $_[1]; $s .= ":Nonblocking" if $_[2]; $acquiring = "$_[0]$s"; my $r = File::Flock->new(@_); $locks{$_[0]} = $s if $r; undef $acquiring; return $r; } sub dounlock { $lastline = (caller())[2]; $releasing = "$_[0]$locks{$_[0]}"; delete $locks{$_[0]}; unlock(@_); undef $releasing; } sub dp { $lastline = (caller())[2]; # debugprint(); } 1;