io-rectitude.c

text/plain

Filename: io-rectitude.c
Type: text/plain
Part: 0
Message: Re: XLogReadRecord() error in XlogReadTwoPhaseData()
/*
 * Stress-test pread(), pwrite(), read(), and write() to detect a few problems
 * with their handling of regular files:
 *
 * - Lack of atomicity.
 *   https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_07
 *   requires atomicity.  (An implementation may always return <= 1; if it
 *   chooses to return higher values, it must maintain atomicity.)
 *
 * - Transiently making readers see zeros when they read concurrently with a
 *   write, even if the file had no zero at that offset before or after the
 *   write.
 *
 * By default, this program will print "mismatch" messages if pwrite() is
 * non-atomic.  Build with other options to test other behaviors:
 * -DCHANGE_CONTENT=0 tests the zeros bug instead of plain atomicity
 * -DUSE_SEEK=1 tests write()/read() instead of pwrite()/pread()
 * -DREOPEN=0 reuses the same file descriptor across iterations
 * -DXLOG_BLCKSZ=32 tests a different byte count
 *    high values may require "ulimit -Ss" changes
 *
 *
 * Observed behaviors:
 *
 * Linux 3.10.0-1160.49.1.el7.x86_64 (CentOS 7.9.2009):
 * pwrite/pread is non-atomic if count>16 (no -D switches)
 * write/read is atomic (-DUSE_SEEK -DXLOG_BLCKSZ=8192000)
 * pwrite/pread is free from zeros bug (-DCHANGE_CONTENT=0)
 * write/read is free from zeros bug (-DUSE_SEEK -DCHANGE_CONTENT=0)
 *
 * Linux 4.9.0-13-sparc64-smp (Debian):
 * pwrite/pread is non-atomic if count>4 (no -D switches)
 * write/read is non-atomic if count>4 (-DUSE_SEEK)
 * write/read IS atomic w/o REOPEN (-DUSE_SEEK -DREOPEN=0 -DXLOG_BLCKSZ=8192000)
 * pwrite/pread has zeros bug for count>127 (-DCHANGE_CONTENT=0)
 * pwrite/pread w/o REOPEN also has zeros bug for count>127 (-DCHANGE_CONTENT=0 -DREOPEN=0)
 * write/read has zeros bug for count>127 (-DUSE_SEEK -DCHANGE_CONTENT=0)
 * write/read w/o REOPEN is free from zeros bug (-DUSE_SEEK -DCHANGE_CONTENT=0 -DREOPEN=0)
 *
 * Linux 5.15.0-2-sparc64-smp (Debian bookworm/sid):
 * pwrite/pread is atomic (-DXLOG_BLCKSZ=8192000)
 * write/read is atomic (-DUSE_SEEK -DXLOG_BLCKSZ=8192000)
 * pwrite/pread is free from zeros bug (-DUSE_SEEK -DCHANGE_CONTENT=0)
 * write/read is free from zeros bug (-DCHANGE_CONTENT=0)
 */

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/* Size of block written in a loop and read in another process's loop. */
#ifndef XLOG_BLCKSZ
#define XLOG_BLCKSZ 8192
#endif
/*
 * Offset of that block within file.  Probably not important.  This was a
 * value seen in a PostgreSQL failure.
 */
#ifndef OFFSET
#define OFFSET 0x72E0A0
#endif
/* Replace pwrite() w/ lseek()+write()?  Likewise pread(). */
#ifndef USE_SEEK
#define USE_SEEK 0
#endif
/*
 * Write a different payload to the block each time?  Disable this to detect
 * the transient-zeros bug.  Keep enabled to detect mere lack of atomicity.
 */
#ifndef CHANGE_CONTENT
#define CHANGE_CONTENT 1
#endif
/* Close and reopen the file before each read or write operation? */
#ifndef REOPEN
#define REOPEN 1
#endif


#if USE_SEEK
static ssize_t
my_pread(int fd, void *buf, size_t count, off_t offset)
{
	if (lseek(fd, offset, SEEK_SET) == (off_t) -1)
		perror("lseek");
	return read(fd, buf, count);
}
#else
static ssize_t
my_pread(int fd, void *buf, size_t count, off_t offset)
{
	return pread(fd, buf, count, offset);
}
#endif

#if USE_SEEK
static ssize_t
my_pwrite(int fd, const void *buf, size_t count, off_t offset)
{
	if (lseek(fd, offset, SEEK_SET) == (off_t) -1)
		perror("lseek");
	return write(fd, buf, count);
}
#else
static ssize_t
my_pwrite(int fd, const void *buf, size_t count, off_t offset)
{
	return pwrite(fd, buf, count, offset);
}
#endif

#if REOPEN
static int
reopen(void)
{
	int			fd = open("io-rectitude.tmp", O_RDWR);

	if (fd == -1)
		perror("open");
	return fd;
}
#endif

static void
writer(int fd)
{
	unsigned char byte_val = 1;

	for (;;)
	{
		char		buf[XLOG_BLCKSZ];

		memset(buf, byte_val, sizeof(buf));
#if REOPEN
		close(fd);
		fd = reopen();
#endif
		errno = 0;
		if (my_pwrite(fd, buf, sizeof(buf), OFFSET) != sizeof(buf))
			perror("pwrite");	/* not a defect */
#if CHANGE_CONTENT
		byte_val++;
#endif
	}
}

static void
reader(int fd)
{
	for (;;)
	{
		unsigned char buf[XLOG_BLCKSZ];
		int			i;
#if REOPEN
		close(fd);
		fd = reopen();
#endif
		errno = 0;
		if (my_pread(fd, buf, sizeof(buf), OFFSET) != sizeof(buf))
			perror("pread");	/* not a defect */
		else
		{
			/* writer() installs the same value in every position. */
			for (i = 0; i < sizeof(buf); ++i)
			{
				if (buf[i] != buf[0])
				{
					fprintf(stderr, "mismatch at %d: %d vs. %d\n",
							i, buf[i], buf[0]);
					break;
				}
			}
		}
	}
}

int
main(int argc, char **argv)
{
	int			fd;
	unsigned char buf[OFFSET + XLOG_BLCKSZ];
	pid_t		pid;

	fd = open("io-rectitude.tmp", O_RDWR | O_CREAT | O_TRUNC, 0666);
	if (fd == -1)
	{
		perror("open");
		return 1;
	}
	/* initialize file contents */
	memset(buf, 1, sizeof(buf));
	if (write(fd, buf, sizeof(buf)) != sizeof(buf))
	{
		perror("write");
		return 1;
	}

	pid = fork();
	if (pid == -1)
	{
		perror("fork");
		return 1;
	}
	else if (pid == 0)
		writer(fd);
	else
		reader(fd);

	return 0;					/* unreachable */
}