Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Postgres/src/bin/pg_basebackup/   (Postgres Database Version 18.4©)  Datei vom 11.4.2026 mit Größe 7 kB image not shown  

 method_worker.c

  Interaktion und
PortierbarkeitC
 

/*-------------------------------------------------------------------------
 *
 * method_worker.c
 *    AIO - perform AIO using worker processes
 *
 * IO workers consume IOs from a shared memory submission queue, run
 * traditional synchronous system calls, and perform the shared completion
 * handling immediately.  Client code submits most requests by pushing IOs
 * into the submission queue, and waits (if necessary) using condition
 * variables.  Some IOs cannot be performed in another process due to lack of
 * infrastructure for reopening the file, and must processed synchronously by
 * the client code when submitted.
 *
 * So that the submitter can make just one system call when submitting a batch
 * of IOs, wakeups "fan out"; each woken IO worker can wake two more. XXX This
 * could be improved by using futexes instead of latches to wake N waiters.
 *
 * This method of AIO is available in all builds on all operating systems, and
 * is the default.
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *   src/backend/storage/aio/method_worker.c
 *
 *-------------------------------------------------------------------------
 */


#include "postgres.h"

#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "port/pg_bitutils.h"
#include "postmaster/auxprocess.h"
#include "postmaster/interrupt.h"
#include "storage/aio.h"
#include "storage/aio_internal.h"
#include "storage/aio_subsys.h"
#include "storage/io_worker.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/injection_point.h"
#include "utils/memdebug.h"
#include "utils/ps_status.h"
#include "utils/wait_event.h"


/* How many workers should each worker wake up if needed? */
#define IO_WORKER_WAKEUP_FANOUT 2


typedef struct PgAioWorkerSubmissionQueue
{
 uint32  size;
 uint32  mask;
 uint32  head;
 uint32  tail;
 int   sqes[FLEXIBLE_ARRAY_MEMBER];
} PgAioWorkerSubmissionQueue;

typedef struct PgAioWorkerSlot
{
 Latch    *latch;
 bool  in_use;
} PgAioWorkerSlot;

typedef struct PgAioWorkerControl
{
 uint64  idle_worker_mask;
 PgAioWorkerSlot workers[FLEXIBLE_ARRAY_MEMBER];
} PgAioWorkerControl;


static size_t pgaio_worker_shmem_size(void);
static void pgaio_worker_shmem_init(bool first_time);

static bool pgaio_worker_needs_synchronous_execution(PgAioHandle *ioh);
static int pgaio_worker_submit(uint16 num_staged_ios, PgAioHandle **staged_ios);


const IoMethodOps pgaio_worker_ops = {
 .shmem_size = pgaio_worker_shmem_size,
 .shmem_init = pgaio_worker_shmem_init,

 .needs_synchronous_execution = pgaio_worker_needs_synchronous_execution,
 .submit = pgaio_worker_submit,
};


/* GUCs */
int   io_workers = 3;


static int io_worker_queue_size = 64;
static int MyIoWorkerId;
static PgAioWorkerSubmissionQueue *io_worker_submission_queue;
static PgAioWorkerControl *io_worker_control;


static size_t
pgaio_worker_queue_shmem_size(int *queue_size)
{
 /* Round size up to next power of two so we can make a mask. */
 *queue_size = pg_nextpower2_32(io_worker_queue_size);

 return offsetof(PgAioWorkerSubmissionQueue, sqes) +
  sizeof(int) * *queue_size;
}

static size_t
pgaio_worker_control_shmem_size(void)
{
 return offsetof(PgAioWorkerControl, workers) +
  sizeof(PgAioWorkerSlot) * MAX_IO_WORKERS;
}

static size_t
pgaio_worker_shmem_size(void)
{
 size_t  sz;
 int   queue_size;

 sz = pgaio_worker_queue_shmem_size(&queue_size);
 sz = add_size(sz, pgaio_worker_control_shmem_size());

 return sz;
}

static void
pgaio_worker_shmem_init(bool first_time)
{
 bool  found;
 int   queue_size;

 io_worker_submission_queue =
  ShmemInitStruct("AioWorkerSubmissionQueue",
      pgaio_worker_queue_shmem_size(&queue_size),
      &found);
 if (!found)
 {
  io_worker_submission_queue->size = queue_size;
  io_worker_submission_queue->head = 0;
  io_worker_submission_queue->tail = 0;
 }

 io_worker_control =
  ShmemInitStruct("AioWorkerControl",
      pgaio_worker_control_shmem_size(),
      &found);
 if (!found)
 {
  io_worker_control->idle_worker_mask = 0;
  for (int i = 0; i < MAX_IO_WORKERS; ++i)
  {
   io_worker_control->workers[i].latch = NULL;
   io_worker_control->workers[i].in_use = false;
  }
 }
}

static int
pgaio_worker_choose_idle(void)
{
 int   worker;

 if (io_worker_control->idle_worker_mask == 0)
  return -1;

 /* Find the lowest bit position, and clear it. */
 worker = pg_rightmost_one_pos64(io_worker_control->idle_worker_mask);
 io_worker_control->idle_worker_mask &= ~(UINT64_C(1) << worker);
 Assert(io_worker_control->workers[worker].in_use);

 return worker;
}

static bool
pgaio_worker_submission_queue_insert(PgAioHandle *ioh)
{
 PgAioWorkerSubmissionQueue *queue;
 uint32  new_head;

 queue = io_worker_submission_queue;
 new_head = (queue->head + 1) & (queue->size - 1);
 if (new_head == queue->tail)
 {
  pgaio_debug(DEBUG3, "io queue is full, at %u elements",
     io_worker_submission_queue->size);
  return false;   /* full */
 }

 queue->sqes[queue->head] = pgaio_io_get_id(ioh);
 queue->head = new_head;

 return true;
}

static int
pgaio_worker_submission_queue_consume(void)
{
 PgAioWorkerSubmissionQueue *queue;
 int   result;

 queue = io_worker_submission_queue;
 if (queue->tail == queue->head)
  return -1;    /* empty */

 result = queue->sqes[queue->tail];
 queue->tail = (queue->tail + 1) & (queue->size - 1);

 return result;
}

static uint32
pgaio_worker_submission_queue_depth(void)
{
 uint32  head;
 uint32  tail;

 head = io_worker_submission_queue->head;
 tail = io_worker_submission_queue->tail;

 if (tail > head)
  head += io_worker_submission_queue->size;

 Assert(head >= tail);

 return head - tail;
}

static bool
pgaio_worker_needs_synchronous_execution(PgAioHandle *ioh)
{
 return
  !IsUnderPostmaster
  || ioh->flags & PGAIO_HF_REFERENCES_LOCAL
  || !pgaio_io_can_reopen(ioh);
}

static void
pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
{
 PgAioHandle *synchronous_ios[PGAIO_SUBMIT_BATCH_SIZE];
 int   nsync = 0;
 Latch    *wakeup = NULL;
 int   worker;

 Assert(num_staged_ios <= PGAIO_SUBMIT_BATCH_SIZE);

 LWLockAcquire(AioWorkerSubmissionQueueLock, LW_EXCLUSIVE);
 for (int i = 0; i < num_staged_ios; ++i)
 {
  Assert(!pgaio_worker_needs_synchronous_execution(staged_ios[i]));
  if (!pgaio_worker_submission_queue_insert(staged_ios[i]))
  {
   /*
    * We'll do it synchronously, but only after we've sent as many as
    * we can to workers, to maximize concurrency.
 */

   synchronous_ios[nsync++] = staged_ios[i];
   continue;
  }

  if (wakeup == NULL)
  {
   /* Choose an idle worker to wake up if we haven't already. */
   worker = pgaio_worker_choose_idle();
   if (worker >= 0)
    wakeup = io_worker_control->workers[worker].latch;

   pgaio_debug_io(DEBUG4, staged_ios[i],
         "choosing worker %d",
         worker);
  }
 }
 LWLockRelease(AioWorkerSubmissionQueueLock);

 if (wakeup)
  SetLatch(wakeup);

 /* Run whatever is left synchronously. */
 if (nsync > 0)
 {
  for (int i = 0; i < nsync; ++i)
  {
   pgaio_io_perform_synchronously(synchronous_ios[i]);
  }
 }
}

static int
pgaio_worker_submit(uint16 num_staged_ios, PgAioHandle **staged_ios)
{
 for (int i = 0; i < num_staged_ios; i++)
 {
  PgAioHandle *ioh = staged_ios[i];

  pgaio_io_prepare_submit(ioh);
 }

 pgaio_worker_submit_internal(num_staged_ios, staged_ios);

 return num_staged_ios;
}

/*
 * on_shmem_exit() callback that releases the worker's slot in
 * io_worker_control.
 */

static void
pgaio_worker_die(int code, Datum arg)
{
 LWLockAcquire(AioWorkerSubmissionQueueLock, LW_EXCLUSIVE);
 Assert(io_worker_control->workers[MyIoWorkerId].in_use);
 Assert(io_worker_control->workers[MyIoWorkerId].latch == MyLatch);

 io_worker_control->idle_worker_mask &= ~(UINT64_C(1) << MyIoWorkerId);
 io_worker_control->workers[MyIoWorkerId].in_use = false;
 io_worker_control->workers[MyIoWorkerId].latch = NULL;
 LWLockRelease(AioWorkerSubmissionQueueLock);
}

/*
 * Register the worker in shared memory, assign MyIoWorkerId and register a
 * shutdown callback to release registration.
 */

static void
pgaio_worker_register(void)
{
 MyIoWorkerId = -1;

 /*
  * XXX: This could do with more fine-grained locking. But it's also not
  * very common for the number of workers to change at the moment...
 */

 LWLockAcquire(AioWorkerSubmissionQueueLock, LW_EXCLUSIVE);

 for (int i = 0; i < MAX_IO_WORKERS; ++i)
 {
  if (!io_worker_control->workers[i].in_use)
  {
   Assert(io_worker_control->workers[i].latch == NULL);
   io_worker_control->workers[i].in_use = true;
   MyIoWorkerId = i;
   break;
  }
  else
   Assert(io_worker_control->workers[i].latch != NULL);
 }

 if (MyIoWorkerId == -1)
  elog(ERROR, "couldn't find a free worker slot");

 io_worker_control->idle_worker_mask |= (UINT64_C(1) << MyIoWorkerId);
 io_worker_control->workers[MyIoWorkerId].latch = MyLatch;
 LWLockRelease(AioWorkerSubmissionQueueLock);

 on_shmem_exit(pgaio_worker_die, 0);
}

static void
pgaio_worker_error_callback(void *arg)
{
 ProcNumber owner;
 PGPROC    *owner_proc;
 int32  owner_pid;
 PgAioHandle *ioh = arg;

 if (!ioh)
  return;

 Assert(ioh->owner_procno != MyProcNumber);
 Assert(MyBackendType == B_IO_WORKER);

 owner = ioh->owner_procno;
 owner_proc = GetPGProcByNumber(owner);
 owner_pid = owner_proc->pid;

 errcontext("I/O worker executing I/O on behalf of process %d", owner_pid);
}

void
IoWorkerMain(const void *startup_data, size_t startup_data_len)
{
 sigjmp_buf local_sigjmp_buf;
 PgAioHandle *volatile error_ioh = NULL;
 ErrorContextCallback errcallback = {0};
 volatile int error_errno = 0;
 char  cmd[128];

 MyBackendType = B_IO_WORKER;
 AuxiliaryProcessMainCommon();

 pqsignal(SIGHUP, SignalHandlerForConfigReload);
 pqsignal(SIGINT, die);  /* to allow manually triggering worker restart */

 /*
  * Ignore SIGTERM, will get explicit shutdown via SIGUSR2 later in the
  * shutdown sequence, similar to checkpointer.
 */

 pqsignal(SIGTERM, SIG_IGN);
 /* SIGQUIT handler was already set up by InitPostmasterChild */
 pqsignal(SIGALRM, SIG_IGN);
 pqsignal(SIGPIPE, SIG_IGN);
 pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);

 /* also registers a shutdown callback to unregister */
 pgaio_worker_register();

 sprintf(cmd, "%d", MyIoWorkerId);
 set_ps_display(cmd);

 errcallback.callback = pgaio_worker_error_callback;
 errcallback.previous = error_context_stack;
 error_context_stack = &errcallback;

 /* see PostgresMain() */
 if (sigsetjmp(local_sigjmp_buf, 1) != 0)
 {
  error_context_stack = NULL;
  HOLD_INTERRUPTS();

  EmitErrorReport();

  /*
   * In the - very unlikely - case that the IO failed in a way that
   * raises an error we need to mark the IO as failed.
   *
   * Need to do just enough error recovery so that we can mark the IO as
   * failed and then exit (postmaster will start a new worker).
 */

  LWLockReleaseAll();

  if (error_ioh != NULL)
  {
   /* should never fail without setting error_errno */
   Assert(error_errno != 0);

   errno = error_errno;

   START_CRIT_SECTION();
   pgaio_io_process_completion(error_ioh, -error_errno);
   END_CRIT_SECTION();
  }

  proc_exit(1);
 }

 /* We can now handle ereport(ERROR) */
 PG_exception_stack = &local_sigjmp_buf;

 sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);

 while (!ShutdownRequestPending)
 {
  uint32  io_index;
  Latch    *latches[IO_WORKER_WAKEUP_FANOUT];
  int   nlatches = 0;
  int   nwakeups = 0;
  int   worker;

  /*
   * Try to get a job to do.
   *
   * The lwlock acquisition also provides the necessary memory barrier
   * to ensure that we don't see an outdated data in the handle.
 */

  LWLockAcquire(AioWorkerSubmissionQueueLock, LW_EXCLUSIVE);
  if ((io_index = pgaio_worker_submission_queue_consume()) == -1)
  {
   /*
    * Nothing to do.  Mark self idle.
    *
    * XXX: Invent some kind of back pressure to reduce useless
    * wakeups?
 */

   io_worker_control->idle_worker_mask |= (UINT64_C(1) << MyIoWorkerId);
  }
  else
  {
   /* Got one.  Clear idle flag. */
   io_worker_control->idle_worker_mask &= ~(UINT64_C(1) << MyIoWorkerId);

   /* See if we can wake up some peers. */
   nwakeups = Min(pgaio_worker_submission_queue_depth(),
         IO_WORKER_WAKEUP_FANOUT);
   for (int i = 0; i < nwakeups; ++i)
   {
    if ((worker = pgaio_worker_choose_idle()) < 0)
     break;
    latches[nlatches++] = io_worker_control->workers[worker].latch;
   }
  }
  LWLockRelease(AioWorkerSubmissionQueueLock);

  for (int i = 0; i < nlatches; ++i)
   SetLatch(latches[i]);

  if (io_index != -1)
  {
   PgAioHandle *ioh = NULL;

   ioh = &pgaio_ctl->io_handles[io_index];
   error_ioh = ioh;
   errcallback.arg = ioh;

   pgaio_debug_io(DEBUG4, ioh,
         "worker %d processing IO",
         MyIoWorkerId);

   /*
    * Prevent interrupts between pgaio_io_reopen() and
    * pgaio_io_perform_synchronously() that otherwise could lead to
    * the FD getting closed in that window.
 */

   HOLD_INTERRUPTS();

   /*
    * It's very unlikely, but possible, that reopen fails. E.g. due
    * to memory allocations failing or file permissions changing or
    * such.  In that case we need to fail the IO.
    *
    * There's not really a good errno we can report here.
 */

   error_errno = ENOENT;
   pgaio_io_reopen(ioh);

   /*
    * To be able to exercise the reopen-fails path, allow injection
    * points to trigger a failure at this point.
 */

   INJECTION_POINT("aio-worker-after-reopen", ioh);

   error_errno = 0;
   error_ioh = NULL;

   /*
    * As part of IO completion the buffer will be marked as NOACCESS,
    * until the buffer is pinned again - which never happens in io
    * workers. Therefore the next time there is IO for the same
    * buffer, the memory will be considered inaccessible. To avoid
    * that, explicitly allow access to the memory before reading data
    * into it.
 */

#ifdef USE_VALGRIND
   {
    struct iovec *iov;
    uint16  iov_length = pgaio_io_get_iovec_length(ioh, &iov);

    for (int i = 0; i < iov_length; i++)
     VALGRIND_MAKE_MEM_UNDEFINED(iov[i].iov_base, iov[i].iov_len);
   }
#endif

   /*
    * We don't expect this to ever fail with ERROR or FATAL, no need
    * to keep error_ioh set to the IO.
    * pgaio_io_perform_synchronously() contains a critical section to
    * ensure we don't accidentally fail.
 */

   pgaio_io_perform_synchronously(ioh);

   RESUME_INTERRUPTS();
   errcallback.arg = NULL;
  }
  else
  {
   WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
       WAIT_EVENT_IO_WORKER_MAIN);
   ResetLatch(MyLatch);
  }

  CHECK_FOR_INTERRUPTS();

  if (ConfigReloadPending)
  {
   ConfigReloadPending = false;
   ProcessConfigFile(PGC_SIGHUP);
  }
 }

 error_context_stack = errcallback.previous;
 proc_exit(0);
}

bool
pgaio_workers_enabled(void)
{
 return io_method == IOMETHOD_WORKER;
}

Messung V0.5 in Prozent
C=73 H=95 G=84

¤ Dauer der Verarbeitung: 0.14 Sekunden  (vorverarbeitet am  2026-08-08) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.