mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	Prevent port collisions between concurrent TAP tests
Currently there is a race condition where if concurrent TAP tests both
test that they can open a port they will assume that it is free and use
it, causing one of them to fail. To prevent this we record a reservation
using an exclusive lock, and any TAP test that discovers a reservation
checks to see if the reserving process is still alive, and looks for
another free port if it is.
Ports are reserved in a directory set by the environment setting
PG_TEST_PORT_DIR, or if that doesn't exist a subdirectory of the top
build directory as set by Makefile.global, or its own
tmp_check directory.
The prove_check recipe in Makefile.global.in is extended to export
top_builddir to the TAP tests. This was already exported by the
prove_installcheck recipes.
Per complaint from Andres Freund
Backpatched from 9b4eafcaf4 to all live branches
Discussion: https://postgr.es/m/20221002164931.d57hlutrcz4d2zi7@awork3.anarazel.de
			
			
This commit is contained in:
		@@ -455,6 +455,7 @@ rm -rf '$(CURDIR)'/tmp_check
 | 
				
			|||||||
$(MKDIR_P) '$(CURDIR)'/tmp_check
 | 
					$(MKDIR_P) '$(CURDIR)'/tmp_check
 | 
				
			||||||
cd $(srcdir) && \
 | 
					cd $(srcdir) && \
 | 
				
			||||||
   TESTDIR='$(CURDIR)' $(with_temp_install) PGPORT='6$(DEF_PGPORT)' \
 | 
					   TESTDIR='$(CURDIR)' $(with_temp_install) PGPORT='6$(DEF_PGPORT)' \
 | 
				
			||||||
 | 
					   top_builddir='$(CURDIR)/$(top_builddir)' \
 | 
				
			||||||
   PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' \
 | 
					   PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' \
 | 
				
			||||||
   $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
 | 
					   $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
 | 
				
			||||||
endef
 | 
					endef
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -90,9 +90,9 @@ use Carp;
 | 
				
			|||||||
use Config;
 | 
					use Config;
 | 
				
			||||||
use Cwd;
 | 
					use Cwd;
 | 
				
			||||||
use Exporter 'import';
 | 
					use Exporter 'import';
 | 
				
			||||||
use Fcntl qw(:mode);
 | 
					use Fcntl qw(:mode :flock :seek :DEFAULT);
 | 
				
			||||||
use File::Basename;
 | 
					use File::Basename;
 | 
				
			||||||
use File::Path qw(rmtree);
 | 
					use File::Path qw(rmtree mkpath);
 | 
				
			||||||
use File::Spec;
 | 
					use File::Spec;
 | 
				
			||||||
use File::stat qw(stat);
 | 
					use File::stat qw(stat);
 | 
				
			||||||
use File::Temp ();
 | 
					use File::Temp ();
 | 
				
			||||||
@@ -110,7 +110,10 @@ our @EXPORT = qw(
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
our ($use_tcp, $test_localhost, $test_pghost, $last_host_assigned,
 | 
					our ($use_tcp, $test_localhost, $test_pghost, $last_host_assigned,
 | 
				
			||||||
	$last_port_assigned, @all_nodes, $died);
 | 
						$last_port_assigned, @all_nodes, $died, $portdir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# list of file reservations made by get_free_port
 | 
				
			||||||
 | 
					my @port_reservation_files;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# For backward compatibility only.
 | 
					# For backward compatibility only.
 | 
				
			||||||
our $vfs_path = '';
 | 
					our $vfs_path = '';
 | 
				
			||||||
@@ -134,6 +137,20 @@ INIT
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	# Tracking of last port value assigned to accelerate free port lookup.
 | 
						# Tracking of last port value assigned to accelerate free port lookup.
 | 
				
			||||||
	$last_port_assigned = int(rand() * 16384) + 49152;
 | 
						$last_port_assigned = int(rand() * 16384) + 49152;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Set the port lock directory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# If we're told to use a directory (e.g. from a buildfarm client)
 | 
				
			||||||
 | 
						# explicitly, use that
 | 
				
			||||||
 | 
						$portdir = $ENV{PG_TEST_PORT_DIR};
 | 
				
			||||||
 | 
						# Otherwise, try to use a directory at the top of the build tree
 | 
				
			||||||
 | 
						# or as a last resort use the tmp_check directory
 | 
				
			||||||
 | 
						my $build_dir = $ENV{top_builddir}
 | 
				
			||||||
 | 
						  || $TestLib::tmp_check ;
 | 
				
			||||||
 | 
						$portdir ||= "$build_dir/portlock";
 | 
				
			||||||
 | 
						$portdir =~ s!\\!/!g;
 | 
				
			||||||
 | 
						# Make sure the directory exists
 | 
				
			||||||
 | 
						mkpath($portdir) unless -d $portdir;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
=pod
 | 
					=pod
 | 
				
			||||||
@@ -1133,8 +1150,8 @@ by test cases that need to start other, non-Postgres servers.
 | 
				
			|||||||
Ports assigned to existing PostgresNode objects are automatically
 | 
					Ports assigned to existing PostgresNode objects are automatically
 | 
				
			||||||
excluded, even if those servers are not currently running.
 | 
					excluded, even if those servers are not currently running.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
XXX A port available now may become unavailable by the time we start
 | 
					The port number is reserved so that other concurrent test programs will not
 | 
				
			||||||
the desired service.
 | 
					try to use the same port.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
=cut
 | 
					=cut
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1183,6 +1200,7 @@ sub get_free_port
 | 
				
			|||||||
					last;
 | 
										last;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								$found = _reserve_port($port) if $found;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1213,6 +1231,40 @@ sub can_bind
 | 
				
			|||||||
	return $ret;
 | 
						return $ret;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Internal routine to reserve a port number
 | 
				
			||||||
 | 
					# Returns 1 if successful, 0 if port is already reserved.
 | 
				
			||||||
 | 
					sub _reserve_port
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						my $port = shift;
 | 
				
			||||||
 | 
						# open in rw mode so we don't have to reopen it and lose the lock
 | 
				
			||||||
 | 
						my $filename = "$portdir/$port.rsv";
 | 
				
			||||||
 | 
						sysopen(my $portfile, $filename, O_RDWR|O_CREAT)
 | 
				
			||||||
 | 
						  || die "opening port file $filename: $!";
 | 
				
			||||||
 | 
						# take an exclusive lock to avoid concurrent access
 | 
				
			||||||
 | 
						flock($portfile, LOCK_EX) || die "locking port file $filename: $!";
 | 
				
			||||||
 | 
						# see if someone else has or had a reservation of this port
 | 
				
			||||||
 | 
						my $pid = <$portfile>;
 | 
				
			||||||
 | 
						chomp $pid;
 | 
				
			||||||
 | 
						if ($pid +0 > 0)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (kill 0, $pid)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								# process exists and is owned by us, so we can't reserve this port
 | 
				
			||||||
 | 
								flock($portfile, LOCK_UN);
 | 
				
			||||||
 | 
								close($portfile);
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						# All good, go ahead and reserve the port
 | 
				
			||||||
 | 
						seek($portfile, 0, SEEK_SET);
 | 
				
			||||||
 | 
						# print the pid with a fixed width so we don't leave any trailing junk
 | 
				
			||||||
 | 
						print $portfile sprintf("%10d\n",$$);
 | 
				
			||||||
 | 
						flock($portfile, LOCK_UN);
 | 
				
			||||||
 | 
						close($portfile);
 | 
				
			||||||
 | 
						push(@port_reservation_files, $filename);
 | 
				
			||||||
 | 
						return 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Automatically shut down any still-running nodes when the test script exits.
 | 
					# Automatically shut down any still-running nodes when the test script exits.
 | 
				
			||||||
# Note that this just stops the postmasters (in the same order the nodes were
 | 
					# Note that this just stops the postmasters (in the same order the nodes were
 | 
				
			||||||
# created in).  Any temporary directories are deleted, in an unspecified
 | 
					# created in).  Any temporary directories are deleted, in an unspecified
 | 
				
			||||||
@@ -1234,6 +1286,8 @@ END
 | 
				
			|||||||
		$node->clean_node if $exit_code == 0 && TestLib::all_tests_passing();
 | 
							$node->clean_node if $exit_code == 0 && TestLib::all_tests_passing();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						unlink @port_reservation_files;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	$? = $exit_code;
 | 
						$? = $exit_code;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user