Приветствую.

Продолжаем изучение исходного кода Postgres. Начинаем с момента форка дочернего процесса из предыдущей статьи. Работаем так же в файле src/backend/postmaster/postmaster.c

Инициализация базового окружения

Любой процесс, породившийся от Postmaster должен инициализировать базовое окружение дочернего процесса. По факту, вызывается функция InitPostmasterChild() (определена в src/backend/utils/init/miscinit.c), выполняющая следующее

Обновление базы стека

Первым делом, обновляется база стека нового процесса.

/*
 * Stack base pointer -- initialized by PostmasterMain and inherited by
 * subprocesses. This is not static because old versions of PL/Java modify
 * it directly. Newer versions use set_stack_base(), but we want to stay
 * binary-compatible for the time being.
 */
char	   *stack_base_ptr = NULL;

Любой дочерний процесс хранит в себе указатель на стек. Зачем? Чтобы отслеживать глубину стека самим, вместо экстренного завершения работы программы при превышении его размера. 

Максимальный размер стека может быть указан в postgresql.conf: параметр max_stack_depth

Инициализация глобальных переменных

Глобальные переменные имеются у каждого процесса. Они определяются в src/backend/utils/init/globals.c, хотя объявляются в src/include/miscadmin.h

Сейчас инициализируются:

  • MyProcId - ID текущего процесса;

  • MyStartTimestamp - штамп времени запуска;

  • MyStartTime - время запуска в формате Postgresql.

Другая неочевидная деталь - рандом. 

Postgres имеет функцию pg_strong_random() для генерации случайных чисел. Объявляется в файле src/include/port.h, а определяется в src/port/pg_strong_random.c

/*
 * ...
 *
 * Returns true on success, and false if none of the sources
 * were available. NB: It is important to check the return value!
 * Proceeding with key generation when no random data was available
 * would lead to predictable keys and security issues.
 */
bool
pg_strong_random(void *buf, size_t len)

Под капотом, идет выбор между 3 стратегиями:

Как понятно из комментария сигнатуры, если не удалось сгенерировать случайную последовательность с помощью сильного рандома, то возвращается false.

Если не удалось сгенерировать случайное число через провайдеров, то придется генерировать сид самим. Для генерации случайного сида, нужны случайные числа. Что может быть случайного? Наш PID и время запуска! 

unsigned int rseed;

if (!pg_strong_random(&rseed, sizeof(rseed)))
{
    /*
     * Since PIDs and timestamps tend to change more frequently in their
     * least significant bits, shift the timestamp left to allow a larger
     * total number of seeds in a given time period.  Since that would
     * leave only 20 bits of the timestamp that cycle every ~1 second,
     * also mix in some higher bits.
     */
    rseed = ((uint64) MyProcPid) ^
        ((uint64) MyStartTimestamp << 12) ^
        ((uint64) MyStartTimestamp >> 20);
}
srandom(rseed);

Обновление обработчиков сигналов

Когда Postmaster закрывается, то вызываются зарегистрированные на это событие функции. Текущему процессу они достались “в наследство”. Некоторые из которых, скорее всего нам не понадобятся. 

В Postmaster был зарегистрирован обработчик для SIGQUIT - пустая функция (игнорирует). Но дочерние процессы не должны так поступать - они должны отвечать незамедлительно.

На данном этапе, SIGQUIT исключается из маски игнорирования сигналов (BlockSig) и регистрируется свой обработчик. 

/*
 * Simple signal handler for exiting quickly as if due to a crash.
 *
 * Normally, this would be used for handling SIGQUIT.
 */
void
SignalHandlerForCrashExit(SIGNAL_ARGS)
{
	/*
	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
	 * because shared memory may be corrupted, so we don't want to try to
	 * clean up our transaction.  Just nail the windows shut and get out of
	 * town.  The callbacks wouldn't be safe to run from a signal handler,
	 * anyway.
	 *
	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
	 * a system reset cycle if someone sends a manual SIGQUIT to a random
	 * backend.  This is necessary precisely because we don't clean up our
	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
	 * should ensure the postmaster sees this as a crash, too, but no harm in
	 * being doubly sure.)
	 */
	_exit(2);
}

Также не забываем, что дочерний процесс зависит от Postmaster только “логически”: если Postmaster завершит исполнение, то дочерний процесс станет зомби. 

Чтобы узнать о завершении работы какого процесса, используются функции prctl() или procctl().

prctl()

prctl() - функция, позволяющая управлять поведением процесса или сигнала. 

#include <sys/prctl.h>

int prctl(int option, unsigned long arg2, unsigned long arg3,
         unsigned long arg4, unsigned long arg5);

В первом аргументе передается событие, которое мы отслеживаем, а дальше аргументы для него.

Например, мы выполняем операции над числами с плавающей точкой и для этого хотим использовать FPU. Что если его нет? В этом случае можно либо эмулировать его через CPU, либо сигнализировать об ошибке. Для подобного можно использовать prctl:

// Эмулировать операции с плавающей точкой без вывода предупреждений
prctl(PR_SET_FPEMU, PR_FPEMU_NOPRINT);

// При операциях с плавающей точкой посылать SIGFPE
prctl(PR_SET_FPEMU, PR_FPEMU_SIGFPE);

procctl()

procctl - системный вызов для управления процессами.

#include <sys/procctl.h>

int
procctl(idtype_t idtype, id_t id, int cmd, void *data);

1 аргумент - на что указывает 2 аргумент: P_PID - PID процесса, P_PGID - ID группы процессов

2 аргумент - ID объекта для отслеживания

3 аргумент - команда

4 аргумент - данные для команды

Например, мы хотим защитить программу от атаки переполнения буфера. Сегодня ОС по умолчанию включают ASLR, но если мы запускаемся на древней системе, то не факт. Чтобы быть уверенными, что она точно включена, то можно сделать такой вызов

// 0 в id - применить к вызывающему процессу
procctl(P_PID, 0, PROC_ASLR_CTL, &PROC_ASLR_FORCE_ENABLE);

Postgres использует любой из них в качестве механизма получения сигнала завершения работы Postmaster, но использовать нужно хотя бы один:

/*
 * Signal handler to be notified if postmaster dies.
 */
#ifdef USE_POSTMASTER_DEATH_SIGNAL
volatile sig_atomic_t postmaster_possibly_dead = false;

static void
postmaster_death_handler(int signo)
{
	postmaster_possibly_dead = true;
}

/*
 * The available signals depend on the OS.  SIGUSR1 and SIGUSR2 are already
 * used for other things, so choose another one.
 *
 * Currently, we assume that we can always find a signal to use.  That
 * seems like a reasonable assumption for all platforms that are modern
 * enough to have a parent-death signaling mechanism.
 */
#if defined(SIGINFO)
#define POSTMASTER_DEATH_SIGNAL SIGINFO
#elif defined(SIGPWR)
#define POSTMASTER_DEATH_SIGNAL SIGPWR
#else
#error "cannot find a signal to use for postmaster death"
#endif

#endif							/* USE_POSTMASTER_DEATH_SIGNAL */

void
PostmasterDeathSignalInit(void)
{
#ifdef USE_POSTMASTER_DEATH_SIGNAL
	int			signum = POSTMASTER_DEATH_SIGNAL;

	/* Register our signal handler. */
	pqsignal(signum, postmaster_death_handler);

	/* Request a signal on parent exit. */
#if defined(PR_SET_PDEATHSIG)
	if (prctl(PR_SET_PDEATHSIG, signum) < 0)
		elog(ERROR, "could not request parent death signal: %m");
#elif defined(PROC_PDEATHSIG_CTL)
	if (procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum) < 0)
		elog(ERROR, "could not request parent death signal: %m");
#else
#error "USE_POSTMASTER_DEATH_SIGNAL set, but there is no mechanism to request the signal"
#endif

	/*
	 * Just in case the parent was gone already and we missed it, we'd better
	 * check the slow way on the first call.
	 */
	postmaster_possibly_dead = true;
#endif							/* USE_POSTMASTER_DEATH_SIGNAL */
}

Инициализация MyLatch

У каждого процесса есть свой локальный Latch, но он не инициализирован. 

Сначала, инициализируется поддержка: для текущей реализации латча вызывается свой “конструктор”.

После инициализируется процессно-локальный Latch - глобальная переменная MyLatch.

Latch - это интерфейс, имитирующий мьютекс. Объявлен в src/include/storage/latch.h, а определяется в src/backend/storage/ipc/latch.c.

Он позволяет процессам засыпать (захватить Latch), пока другой процесс не активирует (освободит) этот Latch.

Это абстрактный тип данных, поддерживающий следующие операции:

/*
 * prototypes for functions in latch.c
 */
extern void InitializeLatchSupport(void);
extern void InitLatch(Latch *latch);
extern void InitSharedLatch(Latch *latch);
extern void OwnLatch(Latch *latch);
extern void DisownLatch(Latch *latch);
extern void SetLatch(Latch *latch);
extern void ResetLatch(Latch *latch);
extern void ShutdownLatchSupport(void);

Лучше рассматривать его как АТД, так как он имеет несколько реализаций:

  • POLL - использует self-pipe trick

self-pipe trick

Сигналы - просто события: было или не было. Если обработчика не зарегистрировано, то оно никем не обрабатывается и летит “в никуда”. Для обхода такой ситуации был придуман self-pipe trick.

Идея проста:

  • Создать переменную-флаг сигнализирующую о том, что сигнал принят (volatile sigset_t is_signal_set)

  • Создать неблокирующую в обе стороны очередь - вызов fcntl(fd, FD_SETFL, O_NONBLOCK), fd - файловый дескриптор очереди

  • Добавляем обработчик нужного сигнала: сигнал поступает - пишем байт в нашу очередь и ставим флаг о том, что сигнал получен

Если sigset_t проставлен, то сколько-нибудь этих сигналов мы приняли. А сколько их было - столько сколько байтов записано в очередь. Читаем и получаем количество полученных сигналов. 

Можно обойтись и без флага, тогда используем poll(). Когда он заканчивает опрашивать, то возвращает число, если очередь была пуста, то возвращается -1 и errno проставляется EINTR, иначе читаем полученные байты.

void
InitializeLatchSupport(void)
{
#if defined(WAIT_USE_SELF_PIPE)
	int			pipefd[2];

	// ...

	/*
	 * Set up the self-pipe that allows a signal handler to wake up the
	 * poll()/epoll_wait() in WaitLatch. Make the write-end non-blocking, so
	 * that SetLatch won't block if the event has already been set many times
	 * filling the kernel buffer. Make the read-end non-blocking too, so that
	 * we can easily clear the pipe by reading until EAGAIN or EWOULDBLOCK.
	 * Also, make both FDs close-on-exec, since we surely do not want any
	 * child processes messing with them.
	 */
	if (pipe(pipefd) < 0)
		elog(FATAL, "pipe() failed: %m");
	if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) == -1)
		elog(FATAL, "fcntl(F_SETFL) failed on read-end of self-pipe: %m");
	if (fcntl(pipefd[1], F_SETFL, O_NONBLOCK) == -1)
		elog(FATAL, "fcntl(F_SETFL) failed on write-end of self-pipe: %m");
	if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) == -1)
		elog(FATAL, "fcntl(F_SETFD) failed on read-end of self-pipe: %m");
	if (fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) == -1)
		elog(FATAL, "fcntl(F_SETFD) failed on write-end of self-pipe: %m");

	selfpipe_readfd = pipefd[0];
	selfpipe_writefd = pipefd[1];
	selfpipe_owner_pid = MyProcPid;
#endif

#ifdef WAIT_USE_SIGNALFD
	// ...
#endif

#ifdef WAIT_USE_KQUEUE
	// ...
#endif
}

  • EPOLL - сигналы обрабатываются как файловые дескрипторы и читаются через epoll()

Как сделать события синхронными

Все сигналы по своей натуре асинхронные - мы не можем предсказать, когда они появятся. Чтобы решить эту проблему была создана функция signalfd()

С помощью нее, сигналы можно обрабатывать как файловые дескрипторы. На практике это означает, что select(), poll(), epoll() и другие функции работающие с файловыми дескрипторами будут работать и с сигналами, только зарегистрировать этот сигнал и получить файловый дескриптор для него. 

И вот мы сделали из асинхронных событий - синхронные. Осталось только передать файловый дескриптор с этим сигналом в epoll(): если сигнал поступил даже ранее чем, мы начали опрашивать, все равно его получим, так как “файл” не пустой

void
InitializeLatchSupport(void)
{
#if defined(WAIT_USE_SELF_PIPE)
  	// ...
#endif

#ifdef WAIT_USE_SIGNALFD
	sigset_t	signalfd_mask;

	/* Block SIGURG, because we'll receive it through a signalfd. */
	sigaddset(&UnBlockSig, SIGURG);

	/* Set up the signalfd to receive SIGURG notifications. */
	sigemptyset(&signalfd_mask);
	sigaddset(&signalfd_mask, SIGURG);
	signal_fd = signalfd(-1, &signalfd_mask, SFD_NONBLOCK | SFD_CLOEXEC);
	if (signal_fd < 0)
		elog(FATAL, "signalfd() failed");
	ReserveExternalFD();
#endif

#ifdef WAIT_USE_KQUEUE
  // ...
#endif
}

* Это Linux-only функция

  • KQUEUE - грубо говоря, epoll() для FreeBSD

  • Windows Events - реализация для Windows, использующая Windows Events

Поддержка нескольких реализаций

Выше было сказано, что есть множество реализаций. Но некоторые из них несовместимы друг с другом. Например, Windows Events и epoll - для одного заголовки присутствуют только в Windows, для другого только в Linux. Как поддерживается несколько реализаций? Препроцессор!

Если интерфейс объявляется в latch.h, то реализации уже в src/backend/storage/ipc/latch.c 

“Полиморфизм” достигается 2 стратегиями:

  • Препроцессор с конкретными реализациями внутри функции напрямую

/*
 * Initialize the process-local latch infrastructure.
 *
 * This must be called once during startup of any process that can wait on
 * latches, before it issues any InitLatch() or OwnLatch() calls.
 */
void
InitializeLatchSupport(void)
{
#if defined(WAIT_USE_SELF_PIPE)
  // ...
#endif

#ifdef WAIT_USE_SIGNALFD
  // ...
#endif

#ifdef WAIT_USE_KQUEUE
  // ...
#endif
}
  • Вызов функций определяемых в своих блоках препроцессора

void
ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
{
	// ...
#if defined(WAIT_USE_EPOLL)
	WaitEventAdjustEpoll(set, event, EPOLL_CTL_MOD);
#elif defined(WAIT_USE_KQUEUE)
	WaitEventAdjustKqueue(set, event, old_events);
#elif defined(WAIT_USE_POLL)
	WaitEventAdjustPoll(set, event);
#elif defined(WAIT_USE_WIN32)
	WaitEventAdjustWin32(set, event);
#endif
}

#if defined(WAIT_USE_EPOLL)
static void
WaitEventAdjustEpoll(WaitEventSet *set, WaitEvent *event, int action)
{
	// ...
}
#endif

#if defined(WAIT_USE_POLL)
static void
WaitEventAdjustPoll(WaitEventSet *set, WaitEvent *event)
{
	// ...
}
#endif

#if defined(WAIT_USE_KQUEUE)
static inline void
WaitEventAdjustKqueueAdd(struct kevent *k_ev, int filter, int action,
						 WaitEvent *event)
{
	// ...
}
#endif

#if defined(WAIT_USE_WIN32)
static void
WaitEventAdjustWin32(WaitEventSet *set, WaitEvent *event)
{
	// ...
}
#endif

Инициализация бэкэнда

Закрытие соединений

От Postmaster мы унаследовали не только обработчики сигналов, но и дескрипторы прослушиваемых сокетов. Дочернему процессу они не нужны, а сокет соединения бэкэнду передали. Поэтому закрываем все соединения.

Отдельно стоит процесс логгера. 

Для логирования используется пара каналов: для чтения и записи. В Postmaster открыт канал в режиме чтения, но логгеру он нужен в режиме записи. Поэтому, закрываем дескриптор канала в режиме чтения (если мы не логгер)

Для отслеживания работоспособности Postmaster используется пайп postmaster_alive_fds.

extern int	postmaster_alive_fds[2];

/*
 * Constants that represent which of postmaster_alive_fds is held by
 * postmaster, and which is used in children to check for postmaster death.
 */
#define POSTMASTER_FD_WATCH		0	/* used in children to check for
									 * postmaster death */
#define POSTMASTER_FD_OWN		1	/* kept open by postmaster only */

Сейчас мы стали дочерним процессом, нам не нужно ничего писать в пайп. Поэтому конец пайпа для записи закрываем. 

Подготовка клиента

Для начала инициализируем все необходимое:

  • Хост и порт подключившегося клиента

  • libpq

  • Направление вывода - ставим DestRemote (пишем все клиенту)

Направление вывода

Во время работы мы постоянно пишем куда-нибудь - консоль (логи), файл (insert, update…), сеть (клиент) или вообще игнорировать вывод

Чтобы понимать куда писать существует перечисление CommandDest

/* ----------------
 *		CommandDest is a simplistic means of identifying the desired
 *		destination.  Someday this will probably need to be improved.
 *
 * Note: only the values DestNone, DestDebug, DestRemote are legal for the
 * global variable whereToSendOutput.   The other values may be used
 * as the destination for individual commands.
 * ----------------
 */
typedef enum
{
	DestNone,					/* results are discarded */
	DestDebug,					/* results go to debugging output */
	DestRemote,					/* results sent to frontend process */
	DestRemoteExecute,			/* sent to frontend, in Execute command */
	DestRemoteSimple,			/* sent to frontend, w/no catalog access */
	DestSPI,					/* results sent to SPI manager */
	DestTuplestore,				/* results sent to Tuplestore */
	DestIntoRel,				/* results sent to relation (SELECT INTO) */
	DestCopyOut,				/* results sent to COPY TO code */
	DestSQLFunction,			/* results sent to SQL-language func mgr */
	DestTransientRel,			/* results sent to transient relation */
	DestTupleQueue				/* results sent to tuple queue */
} CommandDest;

Также при работе самого бэкэнда он тоже пишет куда-нибудь. По сути - синглтон. Для него существует отдельная переменная whereToSendOutput

/* Note: whereToSendOutput is initialized for the bootstrap/standalone case */
CommandDest whereToSendOutput = DestDebug;

Задержка перед запуском

Разработчикам нужно дебажить, а процесс старта, по человеческим меркам, очень быстрый. Именно сейчас нам нужно подождать, чтобы дать возможность подключиться дебагером. Так как дальше пойдет бизнес-логика. 

Для этого можно указать параметр из postgresql.conf pre_auth_delay - количество секунд простоя

Сигналы бэкэнда

На предыдущих шагах мы потерли все сигналы Postmaster и сказали, что установим специфичные для конкретного процесса. Для бэкэнда - SIGTERM

// ...
pqsignal(SIGTERM, process_startup_packet_die);
// ...

static void
process_startup_packet_die(SIGNAL_ARGS)
{
	_exit(1);
}

StartupPacket

Клиент идентифицирован, библиотека инициализирована - мы готовы начать общаться с клиентом. 

Весь процесс получения/обработки пакета имеет таймаут обработки. При его превышении посылается SIGALRM

  1. Принятие пакета.

Прежде чем обрабатывать пакет, его нужно получить. 

На этом этапе могут быть проблемы:

  • Пакета нет

  • Неполный пакет

  • Неверно указана длина пакета

Если этот этап успешно завершен - аллоцируем память под весь пакет и начинаем парсинг

  1. Безопасность.

Подключение может осуществляться по безопасным каналам: SSL (TCP) или GSS-API (UNIX)

Проверка безопасности осуществляется следующим образом.

// Функция парсинга Startup пакета
static int
ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)

2 последних аргумента указывают было ли обработано шифрование. Изначально вызывается как ProcessStartupPacket(myPort, false, false) (SSL и GSS-API не проверены), а уже внутри проверяются условия и при необходимости рекурсивно вызывается эта же самая функция

static int
ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
{
    // ...
  
	if (proto == NEGOTIATE_SSL_CODE && !ssl_done)
	{
		char		SSLok;

#ifdef USE_SSL
		/* No SSL when disabled or on Unix sockets */
		if (!LoadedSSL || IS_AF_UNIX(port->laddr.addr.ss_family))
			SSLok = 'N';
		else
			SSLok = 'S';		/* Support for SSL */
#else
		SSLok = 'N';			/* No support for SSL */
#endif

retry1:
		if (send(port->sock, &SSLok, 1, 0) != 1)
		{
			if (errno == EINTR)
				goto retry1;	/* if interrupted, just retry */
			ereport(COMMERROR,
					(errcode_for_socket_access(),
					 errmsg("failed to send SSL negotiation response: %m")));
			return STATUS_ERROR;	/* close the connection */
		}

#ifdef USE_SSL
		if (SSLok == 'S' && secure_open_server(port) == -1)
			return STATUS_ERROR;
#endif

		return ProcessStartupPacket(port, true, SSLok == 'S');
	}
	else if (proto == NEGOTIATE_GSS_CODE && !gss_done)
	{
		char		GSSok = 'N';

#ifdef ENABLE_GSS
		/* No GSSAPI encryption when on Unix socket */
		if (!IS_AF_UNIX(port->laddr.addr.ss_family))
			GSSok = 'G';
#endif

		while (send(port->sock, &GSSok, 1, 0) != 1)
		{
			if (errno == EINTR)
				continue;
			ereport(COMMERROR,
					(errcode_for_socket_access(),
					 errmsg("failed to send GSSAPI negotiation response: %m")));
			return STATUS_ERROR;	/* close the connection */
		}

#ifdef ENABLE_GSS
		if (GSSok == 'G' && secure_open_gssapi(port) == -1)
			return STATUS_ERROR;
#endif
		// ...
		return ProcessStartupPacket(port, GSSok == 'G', true);
	}
  // ...
}

* Заметьте использование goto при проверке SSL, а для GSS используется while

  1. Парсинг пакета 3-ей версии.

Postgres использует свой [текстовый] протокол для клиент/серверного взаимодействия. 

Протокол текстовый, для его парсинга подходит простая сверка строк и хранение смещения.

Например, это начало цикла, где происходит извлечение названия БД и имени пользователя:

while (offset < len)
{
    char	   *nameptr = buf + offset;
    int32		valoffset;
    char	   *valptr;

    if (*nameptr == '\0')
        break;			/* found packet terminator */
    valoffset = offset + strlen(nameptr) + 1;
    if (valoffset >= len)
        break;			/* missing value, will complain below */
    valptr = buf + valoffset;

    if (strcmp(nameptr, "database") == 0)
        port->database_name = pstrdup(valptr);
    else if (strcmp(nameptr, "user") == 0)
        port->user_name = pstrdup(valptr);
    // ...
}
  1. Постобработка пакета.

Пакет спарсили. Дальше идет постобработка:

  • Присутствие имени пользователя

  • Если название БД не дано, приравнять его к имени пользователя

  • Обработка глобальных/per-db пользователей

  • Отсечение имени пользователя и названия БД до максимально допустимого - 64

  1. Проверка состояния.

Мы все еще зависим от Postmaster. А он находится в разных состояниях, некоторые из которых могут запрещать/отвергать подключения. Поэтому, надо проверить состояние подключения (его задавали во время старта).

Само состояние описывается своим перечислением CAC_state (Client Accept Connection State)

typedef enum CAC_state
{
	CAC_OK,
	CAC_STARTUP,
	CAC_SHUTDOWN,
	CAC_RECOVERY,
	CAC_NOTCONSISTENT,
	CAC_TOOMANY,
	CAC_SUPERUSER
} CAC_state;

Среди всех них, подключение в данный момент допускают только CAC_OK и CAC_SUPERUSER (допуск только суперпользователя), а остальные отбрасываются

switch (port->canAcceptConnections)
{
    case CAC_STARTUP:
        ereport(FATAL,
                (errcode(ERRCODE_CANNOT_CONNECT_NOW),
                 errmsg("the database system is starting up")));
        break;
    case CAC_NOTCONSISTENT:
        if (EnableHotStandby)
            ereport(FATAL,
                    (errcode(ERRCODE_CANNOT_CONNECT_NOW),
                     errmsg("the database system is not yet accepting connections"),
                     errdetail("Consistent recovery state has not been yet reached.")));
        else
            ereport(FATAL,
                    (errcode(ERRCODE_CANNOT_CONNECT_NOW),
                     errmsg("the database system is not accepting connections"),
                     errdetail("Hot standby mode is disabled.")));
        break;
    case CAC_SHUTDOWN:
        ereport(FATAL,
                (errcode(ERRCODE_CANNOT_CONNECT_NOW),
                 errmsg("the database system is shutting down")));
        break;
    case CAC_RECOVERY:
        ereport(FATAL,
                (errcode(ERRCODE_CANNOT_CONNECT_NOW),
                 errmsg("the database system is in recovery mode")));
        break;
    case CAC_TOOMANY:
        ereport(FATAL,
                (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
                 errmsg("sorry, too many clients already")));
        break;
    case CAC_SUPERUSER:
        /* OK for now, will check in InitPostgres */
        break;
    case CAC_OK:
        break;
}
ereport

ereport() - макрос, использующийся для логирования. Объявляется в src/include/utils/elog.h, определяется в src/backend/utils/error/elog.c

/* Error level codes */
#define DEBUG5		10			/* Debugging messages, in categories of
								 * decreasing detail. */
#define DEBUG4		11
#define DEBUG3		12
#define DEBUG2		13
#define DEBUG1		14			/* used by GUC debug_* variables */
#define LOG			15			/* Server operational messages; sent only to
								 * server log by default. */
#define LOG_SERVER_ONLY 16		/* Same as LOG for server reporting, but never
								 * sent to client. */
#define COMMERROR	LOG_SERVER_ONLY /* Client communication problems; same as
									 * LOG for server reporting, but never
									 * sent to client. */
#define INFO		17			/* Messages specifically requested by user (eg
								 * VACUUM VERBOSE output); always sent to
								 * client regardless of client_min_messages,
								 * but by default not sent to server log. */
#define NOTICE		18			/* Helpful messages to users about query
								 * operation; sent to client and not to server
								 * log by default. */
#define WARNING		19			/* Warnings.  NOTICE is for expected messages
								 * like implicit sequence creation by SERIAL.
								 * WARNING is for unexpected messages. */
#define PGWARNING	19			/* Must equal WARNING; see NOTE below. */
#define WARNING_CLIENT_ONLY	20	/* Warnings to be sent to client as usual, but
								 * never to the server log. */
#define ERROR		21			/* user error - abort transaction; return to
								 * known state */
#define PGERROR		21			/* Must equal ERROR; see NOTE below. */
#define FATAL		22			/* fatal error - abort process */
#define PANIC		23			/* take down the other backends with me */

// ...

#define ereport(elevel, ...)	\
	ereport_domain(elevel, TEXTDOMAIN, __VA_ARGS__)

#define TEXTDOMAIN NULL

Первым агрументом передается уровень логирования

Дальше передаются опциональные аргументы. Например, текст сообщения.

Важно то, что если номер уровня логирования не меньше ERROR (21), то макрос не возвращается. Приложение должно завершить работу.

В примере с проверкой CAC_State, если статус не CAC_OK или CAC_USERUSER, то из функции ProcessStartupPacket() мы не вернемся, т.к. логируем с уровнем FATAL: 22 >= 21

Завершение инициализации

Перед началом обработки Startup пакета мы навесили таймаут на обработку. Если дошли до этого момента, то значит успели спарсить пакет и обработчик нужно удалить.

Проверяем успешность обработки Startup пакета и в случае неудачной его обработки тут же завершаем работу. Но завершаем работу со возвращаемым статусом 0.

База данных чаще всего крутится на сервере, а заправляют ими админы. А эти люди чаще пользуются консольными утилитами, например, ps. Для улучшения их жизни добавлен отдельный этап - форматирование заголовка приложения.

Для разных типов процессов - разное форматирование. Сейчас выставляем свой статус.

set_ps_display("initializing");

Старт процесса БД

Процесс проинициализирован. Теперь можно начать работу.

Но не забываем, что контекст памяти с Postmaster у нас до сих пор был общий. 

Сейчас просто убедимся, что вышли из его контекста памяти - проинициализируем свой позже.

MemoryContextSwitchTo(TopMemoryContext);

Точка входа

Мы дошли до точки входа бэкэнда. Функция точки входа принимает в себя не только стандартные argc и argv, но и имя пользователя и название базы данных, к которой нужно подключиться. 

/* ----------------------------------------------------------------
 * PostgresMain
 *	   postgres main loop -- all backends, interactive or otherwise start here
 *
 * argc/argv are the command line arguments to be used.  (When being forked
 * by the postmaster, these are not the original argv array of the process.)
 * dbname is the name of the database to connect to, or NULL if the database
 * name should be extracted from the command line arguments or defaulted.
 * username is the PostgreSQL user name to be used for the session.
 * ----------------------------------------------------------------
 */
void
PostgresMain(int argc, char *argv[],
			 const char *dbname,
			 const char *username)

В 0 части, было сказано, что мы можем запуститься сразу из PostgesMain, минуя Postmaster (ключ –single). Если так, то в название бд передается NULL, а имя пользователя обязательно должно присутствовать. 

else if (argc > 1 && strcmp(argv[1], "--single") == 0)
		PostgresMain(argc, argv,
					 NULL,		/* no dbname */
					 strdup(get_user_name_or_exit(progname)));	

Теперь имя пользователя и название БД у нас присутствует. Поэтому смело передаем их как аргументы и формируем argc/argv

char	   *av[2];
const int	ac = 1;

av[0] = "postgres";
av[1] = NULL;


PostgresMain(ac, av, port->database_name, port->user_name);

Заключение

На этом инициализация интерактивного бэкэнда закончилось.

Дальше - взаимодействие с пользователем и обработка его запросов.

Комментарии (0)