Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |     |   Wurzel  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quellcode-Bibliothek aio.c

  Sprache: C
 

/*-------------------------------------------------------------------------
 *
 * aio.c
 *    AIO - Core Logic
 *
 * For documentation about how AIO works on a higher level, including a
 * schematic example, see README.md.
 *
 *
 * AIO is a complicated subsystem. To keep things navigable, it is split
 * across a number of files:
 *
 * - method_*.c - different ways of executing AIO (e.g. worker process)
 *
 * - aio_target.c - IO on different kinds of targets
 *
 * - aio_io.c - method-independent code for specific IO ops (e.g. readv)
 *
 * - aio_callback.c - callbacks at IO operation lifecycle events
 *
 * - aio_init.c - per-server and per-backend initialization
 *
 * - aio.c - all other topics
 *
 * - read_stream.c - helper for reading buffered relation data
 *
 * - README.md - higher-level overview over AIO
 *
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *    src/backend/storage/aio/aio.c
 *
 *-------------------------------------------------------------------------
 */


#include "postgres.h"

#include "lib/ilist.h"
#include "miscadmin.h"
#include "port/atomics.h"
#include "storage/aio.h"
#include "storage/aio_internal.h"
#include "storage/aio_subsys.h"
#include "utils/guc.h"
#include "utils/guc_hooks.h"
#include "utils/injection_point.h"
#include "utils/resowner.h"
#include "utils/wait_event_types.h"


static inline void pgaio_io_update_state(PgAioHandle *ioh, PgAioHandleState new_state);
static void pgaio_io_reclaim(PgAioHandle *ioh);
static void pgaio_io_resowner_register(PgAioHandle *ioh, struct ResourceOwnerData *resowner);
static void pgaio_io_wait_for_free(void);
static PgAioHandle *pgaio_io_from_wref(PgAioWaitRef *iow, uint64 *ref_generation);
static const char *pgaio_io_state_get_name(PgAioHandleState s);
static void pgaio_io_wait(PgAioHandle *ioh, uint64 ref_generation);


/* Options for io_method. */
const struct config_enum_entry io_method_options[] = {
 {"sync", IOMETHOD_SYNC, false},
 {"worker", IOMETHOD_WORKER, false},
#ifdef IOMETHOD_IO_URING_ENABLED
 {"io_uring", IOMETHOD_IO_URING, false},
#endif
 {NULL, 0false}
};

/* GUCs */
int   io_method = DEFAULT_IO_METHOD;
int   io_max_concurrency = -1;

/* global control for AIO */
PgAioCtl   *pgaio_ctl;

/* current backend's per-backend state */
PgAioBackend *pgaio_my_backend;


static const IoMethodOps *const pgaio_method_ops_table[] = {
 [IOMETHOD_SYNC] = &pgaio_sync_ops,
 [IOMETHOD_WORKER] = &pgaio_worker_ops,
#ifdef IOMETHOD_IO_URING_ENABLED
 [IOMETHOD_IO_URING] = &pgaio_uring_ops,
#endif
};

StaticAssertDecl(lengthof(io_method_options) == lengthof(pgaio_method_ops_table) + 1,
     "io_method_options out of sync with pgaio_method_ops_table");

/* callbacks for the configured io_method, set by assign_io_method */
const IoMethodOps *pgaio_method_ops;


/* --------------------------------------------------------------------------------
 * Public Functions related to PgAioHandle
 * --------------------------------------------------------------------------------
 */


/*
 * Acquire an AioHandle, waiting for IO completion if necessary.
 *
 * Each backend can only have one AIO handle that has been "handed out" to
 * code, but not yet submitted or released. This restriction is necessary to
 * ensure that it is possible for code to wait for an unused handle by waiting
 * for in-flight IO to complete. There is a limited number of handles in each
 * backend, if multiple handles could be handed out without being submitted,
 * waiting for all in-flight IO to complete would not guarantee that handles
 * free up.
 *
 * It is cheap to acquire an IO handle, unless all handles are in use. In that
 * case this function waits for the oldest IO to complete. If that is not
 * desirable, use pgaio_io_acquire_nb().
 *
 * If a handle was acquired but then does not turn out to be needed,
 * e.g. because pgaio_io_acquire() is called before starting an IO in a
 * critical section, the handle needs to be released with pgaio_io_release().
 *
 *
 * To react to the completion of the IO as soon as it is known to have
 * completed, callbacks can be registered with pgaio_io_register_callbacks().
 *
 * To actually execute IO using the returned handle, the pgaio_io_start_*()
 * family of functions is used. In many cases the pgaio_io_start_*() call will
 * not be done directly by code that acquired the handle, but by lower level
 * code that gets passed the handle. E.g. if code in bufmgr.c wants to perform
 * AIO, it typically will pass the handle to smgr.c, which will pass it on to
 * md.c, on to fd.c, which then finally calls pgaio_io_start_*().  This
 * forwarding allows the various layers to react to the IO's completion by
 * registering callbacks. These callbacks in turn can translate a lower
 * layer's result into a result understandable by a higher layer.
 *
 * During pgaio_io_start_*() the IO is staged (i.e. prepared for execution but
 * not submitted to the kernel). Unless in batchmode
 * (c.f. pgaio_enter_batchmode()), the IO will also get submitted for
 * execution. Note that, whether in batchmode or not, the IO might even
 * complete before the functions return.
 *
 * After pgaio_io_start_*() the AioHandle is "consumed" and may not be
 * referenced by the IO issuing code. To e.g. wait for IO, references to the
 * IO can be established with pgaio_io_get_wref() *before* pgaio_io_start_*()
 * is called.  pgaio_wref_wait() can be used to wait for the IO to complete.
 *
 *
 * To know if the IO [partially] succeeded or failed, a PgAioReturn * can be
 * passed to pgaio_io_acquire(). Once the issuing backend has called
 * pgaio_wref_wait(), the PgAioReturn contains information about whether the
 * operation succeeded and details about the first failure, if any. The error
 * can be raised / logged with pgaio_result_report().
 *
 * The lifetime of the memory pointed to be *ret needs to be at least as long
 * as the passed in resowner. If the resowner releases resources before the IO
 * completes (typically due to an error), the reference to *ret will be
 * cleared. In case of resowner cleanup *ret will not be updated with the
 * results of the IO operation.
 */

PgAioHandle *
pgaio_io_acquire(struct ResourceOwnerData *resowner, PgAioReturn *ret)
{
 PgAioHandle *h;

 while (true)
 {
  h = pgaio_io_acquire_nb(resowner, ret);

  if (h != NULL)
   return h;

  /*
   * Evidently all handles by this backend are in use. Just wait for
   * some to complete.
 */

  pgaio_io_wait_for_free();
 }
}

/*
 * Acquire an AioHandle, returning NULL if no handles are free.
 *
 * See pgaio_io_acquire(). The only difference is that this function will return
 * NULL if there are no idle handles, instead of blocking.
 */

PgAioHandle *
pgaio_io_acquire_nb(struct ResourceOwnerData *resowner, PgAioReturn *ret)
{
 PgAioHandle *ioh = NULL;

 if (pgaio_my_backend->num_staged_ios >= PGAIO_SUBMIT_BATCH_SIZE)
 {
  Assert(pgaio_my_backend->num_staged_ios == PGAIO_SUBMIT_BATCH_SIZE);
  pgaio_submit_staged();
 }

 if (pgaio_my_backend->handed_out_io)
  elog(ERROR, "API violation: Only one IO can be handed out");

 /*
  * Probably not needed today, as interrupts should not process this IO,
  * but...
 */

 HOLD_INTERRUPTS();

 if (!dclist_is_empty(&pgaio_my_backend->idle_ios))
 {
  dlist_node *ion = dclist_pop_head_node(&pgaio_my_backend->idle_ios);

  ioh = dclist_container(PgAioHandle, node, ion);

  Assert(ioh->state == PGAIO_HS_IDLE);
  Assert(ioh->owner_procno == MyProcNumber);

  pgaio_io_update_state(ioh, PGAIO_HS_HANDED_OUT);
  pgaio_my_backend->handed_out_io = ioh;

  if (resowner)
   pgaio_io_resowner_register(ioh, resowner);

  if (ret)
  {
   ioh->report_return = ret;
   ret->result.status = PGAIO_RS_UNKNOWN;
  }
 }

 RESUME_INTERRUPTS();

 return ioh;
}

/*
 * Release IO handle that turned out to not be required.
 *
 * See pgaio_io_acquire() for more details.
 */

void
pgaio_io_release(PgAioHandle *ioh)
{
 if (ioh == pgaio_my_backend->handed_out_io)
 {
  Assert(ioh->state == PGAIO_HS_HANDED_OUT);
  Assert(ioh->resowner);

  pgaio_my_backend->handed_out_io = NULL;

  /*
   * Note that no interrupts are processed between the handed_out_io
   * check and the call to reclaim - that's important as otherwise an
   * interrupt could have already reclaimed the handle.
 */

  pgaio_io_reclaim(ioh);
 }
 else
 {
  elog(ERROR, "release in unexpected state");
 }
}

/*
 * Release IO handle during resource owner cleanup.
 */

void
pgaio_io_release_resowner(dlist_node *ioh_node, bool on_error)
{
 PgAioHandle *ioh = dlist_container(PgAioHandle, resowner_node, ioh_node);

 Assert(ioh->resowner);

 /*
  * Otherwise an interrupt, in the middle of releasing the IO, could end up
  * trying to wait for the IO, leading to state confusion.
 */

 HOLD_INTERRUPTS();

 ResourceOwnerForgetAioHandle(ioh->resowner, &ioh->resowner_node);
 ioh->resowner = NULL;

 switch ((PgAioHandleState) ioh->state)
 {
  case PGAIO_HS_IDLE:
   elog(ERROR, "unexpected");
   break;
  case PGAIO_HS_HANDED_OUT:
   Assert(ioh == pgaio_my_backend->handed_out_io || pgaio_my_backend->handed_out_io == NULL);

   if (ioh == pgaio_my_backend->handed_out_io)
   {
    pgaio_my_backend->handed_out_io = NULL;
    if (!on_error)
     elog(WARNING, "leaked AIO handle");
   }

   pgaio_io_reclaim(ioh);
   break;
  case PGAIO_HS_DEFINED:
  case PGAIO_HS_STAGED:
   if (!on_error)
    elog(WARNING, "AIO handle was not submitted");
   pgaio_submit_staged();
   break;
  case PGAIO_HS_SUBMITTED:
  case PGAIO_HS_COMPLETED_IO:
  case PGAIO_HS_COMPLETED_SHARED:
  case PGAIO_HS_COMPLETED_LOCAL:
   /* this is expected to happen */
   break;
 }

 /*
  * Need to unregister the reporting of the IO's result, the memory it's
  * referencing likely has gone away.
 */

 if (ioh->report_return)
  ioh->report_return = NULL;

 RESUME_INTERRUPTS();
}

/*
 * Add a [set of] flags to the IO.
 *
 * Note that this combines flags with already set flags, rather than set flags
 * to explicitly the passed in parameters. This is to allow multiple callsites
 * to set flags.
 */

void
pgaio_io_set_flag(PgAioHandle *ioh, PgAioHandleFlags flag)
{
 Assert(ioh->state == PGAIO_HS_HANDED_OUT);

 ioh->flags |= flag;
}

/*
 * Returns an ID uniquely identifying the IO handle. This is only really
 * useful for logging, as handles are reused across multiple IOs.
 */

int
pgaio_io_get_id(PgAioHandle *ioh)
{
 Assert(ioh >= pgaio_ctl->io_handles &&
     ioh < (pgaio_ctl->io_handles + pgaio_ctl->io_handle_count));
 return ioh - pgaio_ctl->io_handles;
}

/*
 * Return the ProcNumber for the process that can use an IO handle. The
 * mapping from IO handles to PGPROCs is static, therefore this even works
 * when the corresponding PGPROC is not in use.
 */

ProcNumber
pgaio_io_get_owner(PgAioHandle *ioh)
{
 return ioh->owner_procno;
}

/*
 * Return a wait reference for the IO. Only wait references can be used to
 * wait for an IOs completion, as handles themselves can be reused after
 * completion.  See also the comment above pgaio_io_acquire().
 */

void
pgaio_io_get_wref(PgAioHandle *ioh, PgAioWaitRef *iow)
{
 Assert(ioh->state == PGAIO_HS_HANDED_OUT ||
     ioh->state == PGAIO_HS_DEFINED ||
     ioh->state == PGAIO_HS_STAGED);
 Assert(ioh->generation != 0);

 iow->aio_index = ioh - pgaio_ctl->io_handles;
 iow->generation_upper = (uint32) (ioh->generation >> 32);
 iow->generation_lower = (uint32) ioh->generation;
}



/* --------------------------------------------------------------------------------
 * Internal Functions related to PgAioHandle
 * --------------------------------------------------------------------------------
 */


static inline void
pgaio_io_update_state(PgAioHandle *ioh, PgAioHandleState new_state)
{
 /*
  * All callers need to have held interrupts in some form, otherwise
  * interrupt processing could wait for the IO to complete, while in an
  * intermediary state.
 */

 Assert(!INTERRUPTS_CAN_BE_PROCESSED());

 pgaio_debug_io(DEBUG5, ioh,
       "updating state to %s",
       pgaio_io_state_get_name(new_state));

 /*
  * Ensure the changes signified by the new state are visible before the
  * new state becomes visible.
 */

 pg_write_barrier();

 ioh->state = new_state;
}

static void
pgaio_io_resowner_register(PgAioHandle *ioh, struct ResourceOwnerData *resowner)
{
 Assert(!ioh->resowner);
 Assert(resowner);

 ResourceOwnerRememberAioHandle(resowner, &ioh->resowner_node);
 ioh->resowner = resowner;
}

/*
 * Stage IO for execution and, if appropriate, submit it immediately.
 *
 * Should only be called from pgaio_io_start_*().
 */

void
pgaio_io_stage(PgAioHandle *ioh, PgAioOp op)
{
 bool  needs_synchronous;

 Assert(ioh->state == PGAIO_HS_HANDED_OUT);
 Assert(pgaio_my_backend->handed_out_io == ioh);
 Assert(pgaio_io_has_target(ioh));

 /*
  * Otherwise an interrupt, in the middle of staging and possibly executing
  * the IO, could end up trying to wait for the IO, leading to state
  * confusion.
 */

 HOLD_INTERRUPTS();

 ioh->op = op;
 ioh->result = 0;

 pgaio_io_update_state(ioh, PGAIO_HS_DEFINED);

 /* allow a new IO to be staged */
 pgaio_my_backend->handed_out_io = NULL;

 pgaio_io_call_stage(ioh);

 pgaio_io_update_state(ioh, PGAIO_HS_STAGED);

 /*
  * Synchronous execution has to be executed, well, synchronously, so check
  * that first.
 */

 needs_synchronous = pgaio_io_needs_synchronous_execution(ioh);

 pgaio_debug_io(DEBUG3, ioh,
       "staged (synchronous: %d, in_batch: %d)",
       needs_synchronous, pgaio_my_backend->in_batchmode);

 if (!needs_synchronous)
 {
  pgaio_my_backend->staged_ios[pgaio_my_backend->num_staged_ios++] = ioh;
  Assert(pgaio_my_backend->num_staged_ios <= PGAIO_SUBMIT_BATCH_SIZE);

  /*
   * Unless code explicitly opted into batching IOs, submit the IO
   * immediately.
 */

  if (!pgaio_my_backend->in_batchmode)
   pgaio_submit_staged();
 }
 else
 {
  pgaio_io_prepare_submit(ioh);
  pgaio_io_perform_synchronously(ioh);
 }

 RESUME_INTERRUPTS();
}

bool
pgaio_io_needs_synchronous_execution(PgAioHandle *ioh)
{
 /*
  * If the caller said to execute the IO synchronously, do so.
  *
  * XXX: We could optimize the logic when to execute synchronously by first
  * checking if there are other IOs in flight and only synchronously
  * executing if not. Unclear whether that'll be sufficiently common to be
  * worth worrying about.
 */

 if (ioh->flags & PGAIO_HF_SYNCHRONOUS)
  return true;

 /* Check if the IO method requires synchronous execution of IO */
 if (pgaio_method_ops->needs_synchronous_execution)
  return pgaio_method_ops->needs_synchronous_execution(ioh);

 return false;
}

/*
 * Handle IO being processed by IO method.
 *
 * Should be called by IO methods / synchronous IO execution, just before the
 * IO is performed.
 */

void
pgaio_io_prepare_submit(PgAioHandle *ioh)
{
 pgaio_io_update_state(ioh, PGAIO_HS_SUBMITTED);

 dclist_push_tail(&pgaio_my_backend->in_flight_ios, &ioh->node);
}

/*
 * Handle IO getting completed by a method.
 *
 * Should be called by IO methods / synchronous IO execution, just after the
 * IO has been performed.
 *
 * Expects to be called in a critical section. We expect IOs to be usable for
 * WAL etc, which requires being able to execute completion callbacks in a
 * critical section.
 */

void
pgaio_io_process_completion(PgAioHandle *ioh, int result)
{
 Assert(ioh->state == PGAIO_HS_SUBMITTED);

 Assert(CritSectionCount > 0);

 ioh->result = result;

 pgaio_io_update_state(ioh, PGAIO_HS_COMPLETED_IO);

 INJECTION_POINT("aio-process-completion-before-shared", ioh);

 pgaio_io_call_complete_shared(ioh);

 pgaio_io_update_state(ioh, PGAIO_HS_COMPLETED_SHARED);

 /* condition variable broadcast ensures state is visible before wakeup */
 ConditionVariableBroadcast(&ioh->cv);

 /* contains call to pgaio_io_call_complete_local() */
 if (ioh->owner_procno == MyProcNumber)
  pgaio_io_reclaim(ioh);
}

/*
 * Has the IO completed and thus the IO handle been reused?
 *
 * This is useful when waiting for IO completion at a low level (e.g. in an IO
 * method's ->wait_one() callback).
 */

bool
pgaio_io_was_recycled(PgAioHandle *ioh, uint64 ref_generation, PgAioHandleState *state)
{
 *state = ioh->state;

 /*
  * Ensure that we don't see an earlier state of the handle than ioh->state
  * due to compiler or CPU reordering. This protects both ->generation as
  * directly used here, and other fields in the handle accessed in the
  * caller if the handle was not reused.
 */

 pg_read_barrier();

 return ioh->generation != ref_generation;
}

/*
 * Wait for IO to complete. External code should never use this, outside of
 * the AIO subsystem waits are only allowed via pgaio_wref_wait().
 */

static void
pgaio_io_wait(PgAioHandle *ioh, uint64 ref_generation)
{
 PgAioHandleState state;
 bool  am_owner;

 am_owner = ioh->owner_procno == MyProcNumber;

 if (pgaio_io_was_recycled(ioh, ref_generation, &state))
  return;

 if (am_owner)
 {
  if (state != PGAIO_HS_SUBMITTED
   && state != PGAIO_HS_COMPLETED_IO
   && state != PGAIO_HS_COMPLETED_SHARED
   && state != PGAIO_HS_COMPLETED_LOCAL)
  {
   elog(PANIC, "waiting for own IO %d in wrong state: %s",
     pgaio_io_get_id(ioh), pgaio_io_get_state_name(ioh));
  }
 }

 while (true)
 {
  if (pgaio_io_was_recycled(ioh, ref_generation, &state))
   return;

  switch ((PgAioHandleState) state)
  {
   case PGAIO_HS_IDLE:
   case PGAIO_HS_HANDED_OUT:
    elog(ERROR, "IO in wrong state: %d", state);
    break;

   case PGAIO_HS_SUBMITTED:

    /*
     * If we need to wait via the IO method, do so now. Don't
     * check via the IO method if the issuing backend is executing
     * the IO synchronously.
 */

    if (pgaio_method_ops->wait_one && !(ioh->flags & PGAIO_HF_SYNCHRONOUS))
    {
     pgaio_method_ops->wait_one(ioh, ref_generation);
     continue;
    }
    /* fallthrough */

    /* waiting for owner to submit */
   case PGAIO_HS_DEFINED:
   case PGAIO_HS_STAGED:
    /* waiting for reaper to complete */
    /* fallthrough */
   case PGAIO_HS_COMPLETED_IO:
    /* shouldn't be able to hit this otherwise */
    Assert(IsUnderPostmaster);
    /* ensure we're going to get woken up */
    ConditionVariablePrepareToSleep(&ioh->cv);

    while (!pgaio_io_was_recycled(ioh, ref_generation, &state))
    {
     if (state == PGAIO_HS_COMPLETED_SHARED ||
      state == PGAIO_HS_COMPLETED_LOCAL)
      break;
     ConditionVariableSleep(&ioh->cv, WAIT_EVENT_AIO_IO_COMPLETION);
    }

    ConditionVariableCancelSleep();
    break;

   case PGAIO_HS_COMPLETED_SHARED:
   case PGAIO_HS_COMPLETED_LOCAL:

    /*
     * Note that no interrupts are processed between
     * pgaio_io_was_recycled() and this check - that's important
     * as otherwise an interrupt could have already reclaimed the
     * handle.
 */

    if (am_owner)
     pgaio_io_reclaim(ioh);
    return;
  }
 }
}

/*
 * Make IO handle ready to be reused after IO has completed or after the
 * handle has been released without being used.
 *
 * Note that callers need to be careful about only calling this in the right
 * state and that no interrupts can be processed between the state check and
 * the call to pgaio_io_reclaim(). Otherwise interrupt processing could
 * already have reclaimed the handle.
 */

static void
pgaio_io_reclaim(PgAioHandle *ioh)
{
 /* This is only ok if it's our IO */
 Assert(ioh->owner_procno == MyProcNumber);
 Assert(ioh->state != PGAIO_HS_IDLE);

 /* see comment in function header */
 HOLD_INTERRUPTS();

 /*
  * It's a bit ugly, but right now the easiest place to put the execution
  * of local completion callbacks is this function, as we need to execute
  * local callbacks just before reclaiming at multiple callsites.
 */

 if (ioh->state == PGAIO_HS_COMPLETED_SHARED)
 {
  PgAioResult local_result;

  local_result = pgaio_io_call_complete_local(ioh);
  pgaio_io_update_state(ioh, PGAIO_HS_COMPLETED_LOCAL);

  if (ioh->report_return)
  {
   ioh->report_return->result = local_result;
   ioh->report_return->target_data = ioh->target_data;
  }
 }

 pgaio_debug_io(DEBUG4, ioh,
       "reclaiming: distilled_result: (status %s, id %u, error_data %d), raw_result: %d",
       pgaio_result_status_string(ioh->distilled_result.status),
       ioh->distilled_result.id,
       ioh->distilled_result.error_data,
       ioh->result);

 /* if the IO has been defined, it's on the in-flight list, remove */
 if (ioh->state != PGAIO_HS_HANDED_OUT)
  dclist_delete_from(&pgaio_my_backend->in_flight_ios, &ioh->node);

 if (ioh->resowner)
 {
  ResourceOwnerForgetAioHandle(ioh->resowner, &ioh->resowner_node);
  ioh->resowner = NULL;
 }

 Assert(!ioh->resowner);

 /*
  * Update generation & state first, before resetting the IO's fields,
  * otherwise a concurrent "viewer" could think the fields are valid, even
  * though they are being reset.  Increment the generation first, so that
  * we can assert elsewhere that we never wait for an IDLE IO.  While it's
  * a bit weird for the state to go backwards for a generation, it's OK
  * here, as there cannot be references to the "reborn" IO yet.  Can't
  * update both at once, so something has to give.
 */

 ioh->generation++;
 pgaio_io_update_state(ioh, PGAIO_HS_IDLE);

 /* ensure the state update is visible before we reset fields */
 pg_write_barrier();

 ioh->op = PGAIO_OP_INVALID;
 ioh->target = PGAIO_TID_INVALID;
 ioh->flags = 0;
 ioh->num_callbacks = 0;
 ioh->handle_data_len = 0;
 ioh->report_return = NULL;
 ioh->result = 0;
 ioh->distilled_result.status = PGAIO_RS_UNKNOWN;

 /*
  * We push the IO to the head of the idle IO list, that seems more cache
  * efficient in cases where only a few IOs are used.
 */

 dclist_push_head(&pgaio_my_backend->idle_ios, &ioh->node);

 RESUME_INTERRUPTS();
}

/*
 * Wait for an IO handle to become usable.
 *
 * This only really is useful for pgaio_io_acquire().
 */

static void
pgaio_io_wait_for_free(void)
{
 int   reclaimed = 0;

 pgaio_debug(DEBUG2, "waiting for free IO with %d pending, %u in-flight, %u idle IOs",
    pgaio_my_backend->num_staged_ios,
    dclist_count(&pgaio_my_backend->in_flight_ios),
    dclist_count(&pgaio_my_backend->idle_ios));

 /*
  * First check if any of our IOs actually have completed - when using
  * worker, that'll often be the case. We could do so as part of the loop
  * below, but that'd potentially lead us to wait for some IO submitted
  * before.
 */

 for (int i = 0; i < io_max_concurrency; i++)
 {
  PgAioHandle *ioh = &pgaio_ctl->io_handles[pgaio_my_backend->io_handle_off + i];

  if (ioh->state == PGAIO_HS_COMPLETED_SHARED)
  {
   /*
    * Note that no interrupts are processed between the state check
    * and the call to reclaim - that's important as otherwise an
    * interrupt could have already reclaimed the handle.
    *
    * Need to ensure that there's no reordering, in the more common
    * paths, where we wait for IO, that's done by
    * pgaio_io_was_recycled().
 */

   pg_read_barrier();
   pgaio_io_reclaim(ioh);
   reclaimed++;
  }
 }

 if (reclaimed > 0)
  return;

 /*
  * If we have any unsubmitted IOs, submit them now. We'll start waiting in
  * a second, so it's better they're in flight. This also addresses the
  * edge-case that all IOs are unsubmitted.
 */

 if (pgaio_my_backend->num_staged_ios > 0)
  pgaio_submit_staged();

 /* possibly some IOs finished during submission */
 if (!dclist_is_empty(&pgaio_my_backend->idle_ios))
  return;

 if (dclist_count(&pgaio_my_backend->in_flight_ios) == 0)
  ereport(ERROR,
    errmsg_internal("no free IOs despite no in-flight IOs"),
    errdetail_internal("%d pending, %u in-flight, %u idle IOs",
           pgaio_my_backend->num_staged_ios,
           dclist_count(&pgaio_my_backend->in_flight_ios),
           dclist_count(&pgaio_my_backend->idle_ios)));

 /*
  * Wait for the oldest in-flight IO to complete.
  *
  * XXX: Reusing the general IO wait is suboptimal, we don't need to wait
  * for that specific IO to complete, we just need *any* IO to complete.
 */

 {
  PgAioHandle *ioh = dclist_head_element(PgAioHandle, node,
              &pgaio_my_backend->in_flight_ios);
  uint64  generation = ioh->generation;

  switch ((PgAioHandleState) ioh->state)
  {
    /* should not be in in-flight list */
   case PGAIO_HS_IDLE:
   case PGAIO_HS_DEFINED:
   case PGAIO_HS_HANDED_OUT:
   case PGAIO_HS_STAGED:
   case PGAIO_HS_COMPLETED_LOCAL:
    elog(ERROR, "shouldn't get here with io:%d in state %d",
      pgaio_io_get_id(ioh), ioh->state);
    break;

   case PGAIO_HS_COMPLETED_IO:
   case PGAIO_HS_SUBMITTED:
    pgaio_debug_io(DEBUG2, ioh,
          "waiting for free io with %u in flight",
          dclist_count(&pgaio_my_backend->in_flight_ios));

    /*
     * In a more general case this would be racy, because the
     * generation could increase after we read ioh->state above.
     * But we are only looking at IOs by the current backend and
     * the IO can only be recycled by this backend.  Even this is
     * only OK because we get the handle's generation before
     * potentially processing interrupts, e.g. as part of
     * pgaio_debug_io().
 */

    pgaio_io_wait(ioh, generation);
    break;

   case PGAIO_HS_COMPLETED_SHARED:

    /*
     * It's possible that another backend just finished this IO.
     *
     * Note that no interrupts are processed between the state
     * check and the call to reclaim - that's important as
     * otherwise an interrupt could have already reclaimed the
     * handle.
     *
     * Need to ensure that there's no reordering, in the more
     * common paths, where we wait for IO, that's done by
     * pgaio_io_was_recycled().
 */

    pg_read_barrier();
    pgaio_io_reclaim(ioh);
    break;
  }

  if (dclist_count(&pgaio_my_backend->idle_ios) == 0)
   elog(PANIC, "no idle IO after waiting for IO to terminate");
  return;
 }
}

/*
 * Internal - code outside of AIO should never need this and it'd be hard for
 * such code to be safe.
 */

static PgAioHandle *
pgaio_io_from_wref(PgAioWaitRef *iow, uint64 *ref_generation)
{
 PgAioHandle *ioh;

 Assert(iow->aio_index < pgaio_ctl->io_handle_count);

 ioh = &pgaio_ctl->io_handles[iow->aio_index];

 *ref_generation = ((uint64) iow->generation_upper) << 32 |
  iow->generation_lower;

 Assert(*ref_generation != 0);

 return ioh;
}

static const char *
pgaio_io_state_get_name(PgAioHandleState s)
{
#define PGAIO_HS_TOSTR_CASE(sym) case PGAIO_HS_##sym: return #sym
 switch ((PgAioHandleState) s)
 {
   PGAIO_HS_TOSTR_CASE(IDLE);
   PGAIO_HS_TOSTR_CASE(HANDED_OUT);
   PGAIO_HS_TOSTR_CASE(DEFINED);
   PGAIO_HS_TOSTR_CASE(STAGED);
   PGAIO_HS_TOSTR_CASE(SUBMITTED);
   PGAIO_HS_TOSTR_CASE(COMPLETED_IO);
   PGAIO_HS_TOSTR_CASE(COMPLETED_SHARED);
   PGAIO_HS_TOSTR_CASE(COMPLETED_LOCAL);
 }
#undef PGAIO_HS_TOSTR_CASE

 return NULL;    /* silence compiler */
}

const char *
pgaio_io_get_state_name(PgAioHandle *ioh)
{
 return pgaio_io_state_get_name(ioh->state);
}

const char *
pgaio_result_status_string(PgAioResultStatus rs)
{
 switch ((PgAioResultStatus) rs)
 {
  case PGAIO_RS_UNKNOWN:
   return "UNKNOWN";
  case PGAIO_RS_OK:
   return "OK";
  case PGAIO_RS_WARNING:
   return "WARNING";
  case PGAIO_RS_PARTIAL:
   return "PARTIAL";
  case PGAIO_RS_ERROR:
   return "ERROR";
 }

 return NULL;    /* silence compiler */
}



/* --------------------------------------------------------------------------------
 * Functions primarily related to IO Wait References
 * --------------------------------------------------------------------------------
 */


/*
 * Mark a wait reference as invalid
 */

void
pgaio_wref_clear(PgAioWaitRef *iow)
{
 iow->aio_index = PG_UINT32_MAX;
}

/* Is the wait reference valid? */
bool
pgaio_wref_valid(PgAioWaitRef *iow)
{
 return iow->aio_index != PG_UINT32_MAX;
}

/*
 * Similar to pgaio_io_get_id(), just for wait references.
 */

int
pgaio_wref_get_id(PgAioWaitRef *iow)
{
 Assert(pgaio_wref_valid(iow));
 return iow->aio_index;
}

/*
 * Wait for the IO to have completed. Can be called in any process, not just
 * in the issuing backend.
 */

void
pgaio_wref_wait(PgAioWaitRef *iow)
{
 uint64  ref_generation;
 PgAioHandle *ioh;

 ioh = pgaio_io_from_wref(iow, &ref_generation);

 pgaio_io_wait(ioh, ref_generation);
}

/*
 * Check if the referenced IO completed, without blocking.
 */

bool
pgaio_wref_check_done(PgAioWaitRef *iow)
{
 uint64  ref_generation;
 PgAioHandleState state;
 bool  am_owner;
 PgAioHandle *ioh;

 ioh = pgaio_io_from_wref(iow, &ref_generation);

 if (pgaio_io_was_recycled(ioh, ref_generation, &state))
  return true;

 if (state == PGAIO_HS_IDLE)
  return true;

 am_owner = ioh->owner_procno == MyProcNumber;

 if (state == PGAIO_HS_COMPLETED_SHARED ||
  state == PGAIO_HS_COMPLETED_LOCAL)
 {
  /*
   * Note that no interrupts are processed between
   * pgaio_io_was_recycled() and this check - that's important as
   * otherwise an interrupt could have already reclaimed the handle.
 */

  if (am_owner)
   pgaio_io_reclaim(ioh);
  return true;
 }

 /*
  * XXX: It likely would be worth checking in with the io method, to give
  * the IO method a chance to check if there are completion events queued.
 */


 return false;
}



/* --------------------------------------------------------------------------------
 * Actions on multiple IOs.
 * --------------------------------------------------------------------------------
 */


/*
 * Submit IOs in batches going forward.
 *
 * Submitting multiple IOs at once can be substantially faster than doing so
 * one-by-one. At the same time, submitting multiple IOs at once requires more
 * care to avoid deadlocks.
 *
 * Consider backend A staging an IO for buffer 1 and then trying to start IO
 * on buffer 2, while backend B does the inverse. If A submitted the IO before
 * moving on to buffer 2, this works just fine, B will wait for the IO to
 * complete. But if batching were used, each backend will wait for IO that has
 * not yet been submitted to complete, i.e. forever.
 *
 * End batch submission mode with pgaio_exit_batchmode().  (Throwing errors is
 * allowed; error recovery will end the batch.)
 *
 * To avoid deadlocks, code needs to ensure that it will not wait for another
 * backend while there is unsubmitted IO. E.g. by using conditional lock
 * acquisition when acquiring buffer locks. To check if there currently are
 * staged IOs, call pgaio_have_staged() and to submit all staged IOs call
 * pgaio_submit_staged().
 *
 * It is not allowed to enter batchmode while already in batchmode, it's
 * unlikely to ever be needed, as code needs to be explicitly aware of being
 * called in batchmode, to avoid the deadlock risks explained above.
 *
 * Note that IOs may get submitted before pgaio_exit_batchmode() is called,
 * e.g. because too many IOs have been staged or because pgaio_submit_staged()
 * was called.
 */

void
pgaio_enter_batchmode(void)
{
 if (pgaio_my_backend->in_batchmode)
  elog(ERROR, "starting batch while batch already in progress");
 pgaio_my_backend->in_batchmode = true;
}

/*
 * Stop submitting IOs in batches.
 */

void
pgaio_exit_batchmode(void)
{
 Assert(pgaio_my_backend->in_batchmode);

 pgaio_submit_staged();
 pgaio_my_backend->in_batchmode = false;
}

/*
 * Are there staged but unsubmitted IOs?
 *
 * See comment above pgaio_enter_batchmode() for why code may need to check if
 * there is IO in that state.
 */

bool
pgaio_have_staged(void)
{
 Assert(pgaio_my_backend->in_batchmode ||
     pgaio_my_backend->num_staged_ios == 0);
 return pgaio_my_backend->num_staged_ios > 0;
}

/*
 * Submit all staged but not yet submitted IOs.
 *
 * Unless in batch mode, this never needs to be called, as IOs get submitted
 * as soon as possible. While in batchmode pgaio_submit_staged() can be called
 * before waiting on another backend, to avoid the risk of deadlocks. See
 * pgaio_enter_batchmode().
 */

void
pgaio_submit_staged(void)
{
 int   total_submitted = 0;
 int   did_submit;

 if (pgaio_my_backend->num_staged_ios == 0)
  return;


 START_CRIT_SECTION();

 did_submit = pgaio_method_ops->submit(pgaio_my_backend->num_staged_ios,
            pgaio_my_backend->staged_ios);

 END_CRIT_SECTION();

 total_submitted += did_submit;

 Assert(total_submitted == did_submit);

 pgaio_my_backend->num_staged_ios = 0;

 pgaio_debug(DEBUG4,
    "aio: submitted %d IOs",
    total_submitted);
}



/* --------------------------------------------------------------------------------
 * Other
 * --------------------------------------------------------------------------------
 */



/*
 * Perform AIO related cleanup after an error.
 *
 * This should be called early in the error recovery paths, as later steps may
 * need to issue AIO (e.g. to record a transaction abort WAL record).
 */

void
pgaio_error_cleanup(void)
{
 /*
  * It is possible that code errored out after pgaio_enter_batchmode() but
  * before pgaio_exit_batchmode() was called. In that case we need to
  * submit the IO now.
 */

 if (pgaio_my_backend->in_batchmode)
 {
  pgaio_my_backend->in_batchmode = false;

  pgaio_submit_staged();
 }

 /*
  * As we aren't in batchmode, there shouldn't be any unsubmitted IOs.
 */

 Assert(pgaio_my_backend->num_staged_ios == 0);
}

/*
 * Perform AIO related checks at (sub-)transactional boundaries.
 *
 * This should be called late during (sub-)transactional commit/abort, after
 * all steps that might need to perform AIO, so that we can verify that the
 * AIO subsystem is in a valid state at the end of a transaction.
 */

void
AtEOXact_Aio(bool is_commit)
{
 /*
  * We should never be in batch mode at transactional boundaries. In case
  * an error was thrown while in batch mode, pgaio_error_cleanup() should
  * have exited batchmode.
  *
  * In case we are in batchmode somehow, make sure to submit all staged
  * IOs, other backends may need them to complete to continue.
 */

 if (pgaio_my_backend->in_batchmode)
 {
  pgaio_error_cleanup();
  elog(WARNING, "open AIO batch at end of (sub-)transaction");
 }

 /*
  * As we aren't in batchmode, there shouldn't be any unsubmitted IOs.
 */

 Assert(pgaio_my_backend->num_staged_ios == 0);
}

/*
 * Need to submit staged but not yet submitted IOs using the fd, otherwise
 * the IO would end up targeting something bogus.
 */

void
pgaio_closing_fd(int fd)
{
 /*
  * Might be called before AIO is initialized or in a subprocess that
  * doesn't use AIO.
 */

 if (!pgaio_my_backend)
  return;

 /*
  * For now just submit all staged IOs - we could be more selective, but
  * it's probably not worth it.
 */

 if (pgaio_my_backend->num_staged_ios > 0)
 {
  pgaio_debug(DEBUG2,
     "submitting %d IOs before FD %d gets closed",
     pgaio_my_backend->num_staged_ios, fd);
  pgaio_submit_staged();
 }

 /*
  * If requested by the IO method, wait for all IOs that use the
  * to-be-closed FD.
 */

 if (pgaio_method_ops->wait_on_fd_before_close)
 {
  /*
   * As waiting for one IO to complete may complete multiple IOs, we
   * can't just use a mutable list iterator. The maximum number of
   * in-flight IOs is fairly small, so just restart the loop after
   * waiting for an IO.
 */

  while (!dclist_is_empty(&pgaio_my_backend->in_flight_ios))
  {
   dlist_iter iter;
   PgAioHandle *ioh = NULL;
   uint64  generation;

   dclist_foreach(iter, &pgaio_my_backend->in_flight_ios)
   {
    ioh = dclist_container(PgAioHandle, node, iter.cur);

    generation = ioh->generation;

    if (pgaio_io_uses_fd(ioh, fd))
     break;
    else
     ioh = NULL;
   }

   if (!ioh)
    break;

   pgaio_debug_io(DEBUG2, ioh,
         "waiting for IO before FD %d gets closed, %u in-flight IOs",
         fd, dclist_count(&pgaio_my_backend->in_flight_ios));

   /* see comment in pgaio_io_wait_for_free() about raciness */
   pgaio_io_wait(ioh, generation);
  }
 }
}

/*
 * Registered as before_shmem_exit() callback in pgaio_init_backend()
 */

void
pgaio_shutdown(int code, Datum arg)
{
 Assert(pgaio_my_backend);
 Assert(!pgaio_my_backend->handed_out_io);

 /* first clean up resources as we would at a transaction boundary */
 AtEOXact_Aio(code == 0);

 /*
  * Before exiting, make sure that all IOs are finished. That has two main
  * purposes:
  *
  * - Some kernel-level AIO mechanisms don't deal well with the issuer of
  * an AIO exiting before IO completed
  *
  * - It'd be confusing to see partially finished IOs in stats views etc
 */

 while (!dclist_is_empty(&pgaio_my_backend->in_flight_ios))
 {
  PgAioHandle *ioh = dclist_head_element(PgAioHandle, node, &pgaio_my_backend->in_flight_ios);
  uint64  generation = ioh->generation;

  pgaio_debug_io(DEBUG2, ioh,
        "waiting for IO to complete during shutdown, %u in-flight IOs",
        dclist_count(&pgaio_my_backend->in_flight_ios));

  /* see comment in pgaio_io_wait_for_free() about raciness */
  pgaio_io_wait(ioh, generation);
 }

 pgaio_my_backend = NULL;
}

void
assign_io_method(int newval, void *extra)
{
 Assert(newval < lengthof(pgaio_method_ops_table));
 Assert(pgaio_method_ops_table[newval] != NULL);

 pgaio_method_ops = pgaio_method_ops_table[newval];
}

bool
check_io_max_concurrency(int *newval, void **extra, GucSource source)
{
 if (*newval == -1)
 {
  /*
   * Auto-tuning will be applied later during startup, as auto-tuning
   * depends on the value of various GUCs.
 */

  return true;
 }
 else if (*newval == 0)
 {
  GUC_check_errdetail("Only -1 or values bigger than 0 are valid.");
  return false;
 }

 return true;
}

Messung V0.5 in Prozent
C=93 H=88 G=90

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.39Angebot  (Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-08) ¤

*Eine klare Vorstellung vom Zielzustand






Versionsinformation zu Columbo

Bemerkung:

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Anfrage:

Dauer der Verarbeitung:

Sekunden

sprechenden Kalenders






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

letze Version des Elbe Quellennavigators


Jenseits des Üblichen ....
    

Besucher

Besucher

Statistik
#Sources=141584
#Domains=752002