/* * Atomically increments the pincount on a given entry. * Returns the value of the pincount after the increment. */ static int32 SyncHTAddRef(SyncHT *syncHT, void *entry) { int32 *pinCountPtr = (int32 *) ((char *) entry + syncHT->pinCountOffset); pg_atomic_add_fetch_u32((pg_atomic_uint32 *)pinCountPtr,1); return *pinCountPtr; }
/** * This method is used by a backend to switch the group leader. It is unique * in that it modifies the numFollowers field in its current group leader and new leader index. * The increments and decrements are done using atomic operations (else we may have race conditions * across processes). However, this code is not thread safe. We do not call these code in multi-threaded * situations. */ static inline void SwitchGroupLeader(int newLeaderIndex) { BackoffBackendSharedEntry *myEntry = myBackoffSharedEntry(); BackoffBackendSharedEntry *oldLeaderEntry = NULL; BackoffBackendSharedEntry *newLeaderEntry = NULL; if (backoffSingleton->sweeperInProgress == true) return; Assert(newLeaderIndex < myEntry->groupLeaderIndex); Assert(newLeaderIndex >= 0 && newLeaderIndex < backoffSingleton->numEntries); oldLeaderEntry = &backoffSingleton->backendEntries[myEntry->groupLeaderIndex]; newLeaderEntry = &backoffSingleton->backendEntries[newLeaderIndex]; pg_atomic_sub_fetch_u32((pg_atomic_uint32 *) &oldLeaderEntry->numFollowers, 1); pg_atomic_add_fetch_u32((pg_atomic_uint32 *) &newLeaderEntry->numFollowers, 1); myEntry->groupLeaderIndex = newLeaderIndex; }
/* * thread_DispatchCommand is the thread proc used to dispatch the command to one or more of the qExecs. * * NOTE: This function MUST NOT contain elog or ereport statements. (or most any other backend code) * elog is NOT thread-safe. Developers should instead use something like: * * if (DEBUG3 >= log_min_messages) * write_log("my brilliant log statement here."); * * NOTE: In threads, we cannot use palloc, because it's not thread safe. */ static void * thread_DispatchCommand(void *arg) { DispatchCommandParms *pParms = (DispatchCommandParms *) arg; gp_set_thread_sigmasks(); /* * Mark that we are runnig a new thread. The main thread will check * it to see if there is still alive one. Let's do this after we block * signals so that nobody will intervent and mess up the value. * (should we actually block signals before spawning a thread, as much * like we do in fork??) */ pg_atomic_add_fetch_u32((pg_atomic_uint32 *) &RunningThreadCount, 1); /* * We need to make sure the value will be decremented once the thread * finishes. Currently there is not such case but potentially we could * have pthread_exit or thread cancellation in the middle of code, in * which case we would miss to decrement value if we tried to do this * without the cleanup callback facility. */ pthread_cleanup_push(DecrementRunningCount, NULL); { thread_DispatchOut(pParms); /* * thread_DispatchWaitSingle might have a problem with interupts */ if (pParms->db_count == 1 && false) thread_DispatchWaitSingle(pParms); else thread_DispatchWait(pParms); } pthread_cleanup_pop(1); return (NULL); }
static void test_atomic_uint32(void) { pg_atomic_uint32 var; uint32 expected; int i; pg_atomic_init_u32(&var, 0); if (pg_atomic_read_u32(&var) != 0) elog(ERROR, "atomic_read_u32() #1 wrong"); pg_atomic_write_u32(&var, 3); if (pg_atomic_read_u32(&var) != 3) elog(ERROR, "atomic_read_u32() #2 wrong"); if (pg_atomic_fetch_add_u32(&var, 1) != 3) elog(ERROR, "atomic_fetch_add_u32() #1 wrong"); if (pg_atomic_fetch_sub_u32(&var, 1) != 4) elog(ERROR, "atomic_fetch_sub_u32() #1 wrong"); if (pg_atomic_sub_fetch_u32(&var, 3) != 0) elog(ERROR, "atomic_sub_fetch_u32() #1 wrong"); if (pg_atomic_add_fetch_u32(&var, 10) != 10) elog(ERROR, "atomic_add_fetch_u32() #1 wrong"); if (pg_atomic_exchange_u32(&var, 5) != 10) elog(ERROR, "pg_atomic_exchange_u32() #1 wrong"); if (pg_atomic_exchange_u32(&var, 0) != 5) elog(ERROR, "pg_atomic_exchange_u32() #0 wrong"); /* test around numerical limits */ if (pg_atomic_fetch_add_u32(&var, INT_MAX) != 0) elog(ERROR, "pg_atomic_fetch_add_u32() #2 wrong"); if (pg_atomic_fetch_add_u32(&var, INT_MAX) != INT_MAX) elog(ERROR, "pg_atomic_add_fetch_u32() #3 wrong"); pg_atomic_fetch_add_u32(&var, 1); /* top up to UINT_MAX */ if (pg_atomic_read_u32(&var) != UINT_MAX) elog(ERROR, "atomic_read_u32() #2 wrong"); if (pg_atomic_fetch_sub_u32(&var, INT_MAX) != UINT_MAX) elog(ERROR, "pg_atomic_fetch_sub_u32() #2 wrong"); if (pg_atomic_read_u32(&var) != (uint32) INT_MAX + 1) elog(ERROR, "atomic_read_u32() #3 wrong: %u", pg_atomic_read_u32(&var)); expected = pg_atomic_sub_fetch_u32(&var, INT_MAX); if (expected != 1) elog(ERROR, "pg_atomic_sub_fetch_u32() #3 wrong: %u", expected); pg_atomic_sub_fetch_u32(&var, 1); /* fail exchange because of old expected */ expected = 10; if (pg_atomic_compare_exchange_u32(&var, &expected, 1)) elog(ERROR, "atomic_compare_exchange_u32() changed value spuriously"); /* CAS is allowed to fail due to interrupts, try a couple of times */ for (i = 0; i < 1000; i++) { expected = 0; if (!pg_atomic_compare_exchange_u32(&var, &expected, 1)) break; } if (i == 1000) elog(ERROR, "atomic_compare_exchange_u32() never succeeded"); if (pg_atomic_read_u32(&var) != 1) elog(ERROR, "atomic_compare_exchange_u32() didn't set value properly"); pg_atomic_write_u32(&var, 0); /* try setting flagbits */ if (pg_atomic_fetch_or_u32(&var, 1) & 1) elog(ERROR, "pg_atomic_fetch_or_u32() #1 wrong"); if (!(pg_atomic_fetch_or_u32(&var, 2) & 1)) elog(ERROR, "pg_atomic_fetch_or_u32() #2 wrong"); if (pg_atomic_read_u32(&var) != 3) elog(ERROR, "invalid result after pg_atomic_fetch_or_u32()"); /* try clearing flagbits */ if ((pg_atomic_fetch_and_u32(&var, ~2) & 3) != 3) elog(ERROR, "pg_atomic_fetch_and_u32() #1 wrong"); if (pg_atomic_fetch_and_u32(&var, ~1) != 1) elog(ERROR, "pg_atomic_fetch_and_u32() #2 wrong: is %u", pg_atomic_read_u32(&var)); /* no bits set anymore */ if (pg_atomic_fetch_and_u32(&var, ~0) != 0) elog(ERROR, "pg_atomic_fetch_and_u32() #3 wrong"); }
/* * Atomically increments the performance counter * * delta must be positive */ void Cache_AddPerfCounter(uint32 *counter, int delta) { Assert(counter + delta >= 0); pg_atomic_add_fetch_u32((pg_atomic_uint32 *) counter,delta); }
/* * Grabs one entry in the sessionStateArray for current session. * If the current session already has an entry, it just returns the * pointer to the previously grabbed entry. */ static SessionState* SessionState_Acquire(int sessionId) { LWLockAcquire(SessionStateLock, LW_EXCLUSIVE); SessionState *cur = AllSessionStateEntries->usedList; while (cur != NULL && cur->sessionId != sessionId) { Assert(INVALID_SESSION_ID != cur->sessionId); cur = cur->next; } if (NULL == cur && NULL == AllSessionStateEntries->freeList) { LWLockRelease(SessionStateLock); ereport(FATAL, (errcode(ERRCODE_TOO_MANY_CONNECTIONS), errmsg("Too many sessions."), errdetail("Could not acquire resources for additional sessions."), errhint("Disconnect some sessions and try again."))); } SessionState *acquired = cur; /* * Nothing was acquired for this session from any other processes. Therefore, * acquire a new entry, and reset its properties. */ if (NULL == acquired) { acquired = AllSessionStateEntries->freeList; Assert(INVALID_SESSION_ID == acquired->sessionId && acquired->runawayStatus == RunawayStatus_NotRunaway && 0 == acquired->pinCount && CLEANUP_COUNTDOWN_BEFORE_RUNAWAY == acquired->cleanupCountdown && 0 == acquired->activeProcessCount && 0 == acquired->sessionVmem && 0 == acquired->spinLock && 0 == acquired->sessionVmemRunaway && 0 == acquired->commandCountRunaway && !acquired->isModifiedSessionId); AllSessionStateEntries->freeList = acquired->next; acquired->next = AllSessionStateEntries->usedList; AllSessionStateEntries->usedList = acquired; AllSessionStateEntries->numSession++; Assert(AllSessionStateEntries->numSession <= AllSessionStateEntries->maxSession); acquired->sessionId = sessionId; acquired->runawayStatus = RunawayStatus_NotRunaway; acquired->sessionVmemRunaway = 0; acquired->commandCountRunaway = 0; acquired->pinCount = 0; acquired->sessionVmem = 0; acquired->cleanupCountdown = CLEANUP_COUNTDOWN_BEFORE_RUNAWAY; acquired->activeProcessCount = 0; acquired->idle_start = 0; acquired->resGroupSlot = NULL; #ifdef USE_ASSERT_CHECKING acquired->isModifiedSessionId = false; #endif /* * Make sure that the lock is reset to released. Note: this doesn't * have a matching SpinLockAcquire. We are just resetting the lock * as part of initialization */ SpinLockRelease(&acquired->spinLock); } Assert(NULL != acquired); int pinCount = pg_atomic_add_fetch_u32((pg_atomic_uint32 *) &acquired->pinCount, 1); ereport(gp_sessionstate_loglevel, (errmsg("SessionState_Acquire: pinCount: %d, activeProcessCount: %d", pinCount, acquired->activeProcessCount), errprintstack(true))); LWLockRelease(SessionStateLock); return acquired; }
/* * Marks the current process as clean. If all the processes are marked * as clean for this session (i.e., cleanupCountdown == 0 in the * MySessionState) then we reset session's runaway status as well as * the runaway detector flag (i.e., a new runaway detector can run). * * Parameters: * ignoredCleanup: whether the cleanup was ignored, i.e., no elog(ERROR, ...) * was thrown. In such case a deactivated process is not reactivated as the * deactivation didn't get interrupted. */ void RunawayCleaner_RunawayCleanupDoneForProcess(bool ignoredCleanup) { /* * We don't do anything if we don't have an ongoing cleanup, or we already finished * cleanup once for the current runaway event */ if (beginCleanupRunawayVersion != *latestRunawayVersion || endCleanupRunawayVersion == beginCleanupRunawayVersion) { /* Either we never started cleanup, or we already finished */ return; } /* Disable repeating call */ endCleanupRunawayVersion = beginCleanupRunawayVersion; Assert(NULL != MySessionState); /* * As the current cleanup holds leverage on the cleanupCountdown, * the session must stay as runaway at least until the current * process marks itself clean */ Assert(MySessionState->runawayStatus != RunawayStatus_NotRunaway); /* We only cleanup if we were active when the runaway event happened */ Assert((!isProcessActive && *latestRunawayVersion < deactivationVersion && *latestRunawayVersion > activationVersion) || (*latestRunawayVersion > activationVersion && (activationVersion >= deactivationVersion && isProcessActive))); /* * We don't reactivate if the process is already active or a deactivated * process never errored out during deactivation (i.e., failed to complete * deactivation) */ if (!isProcessActive && !ignoredCleanup) { Assert(1 == *isRunawayDetector); Assert(0 < MySessionState->cleanupCountdown); /* * As the process threw ERROR instead of going into ReadCommand() blocking * state, we have to reactivate the process from its current Deactivated * state */ IdleTracker_ActivateProcess(); } Assert(0 < MySessionState->cleanupCountdown); #if USE_ASSERT_CHECKING int cleanProgress = #endif pg_atomic_add_fetch_u32((pg_atomic_uint32 *)&MySessionState->cleanupCountdown, -1); Assert(0 <= cleanProgress); uint32 expected = 0; bool finalCleaner = pg_atomic_compare_exchange_u32((pg_atomic_uint32 *) &MySessionState->cleanupCountdown, &expected, CLEANUP_COUNTDOWN_BEFORE_RUNAWAY); if (finalCleaner) { /* * The final cleaner is responsible to reset the runaway flag, * and enable the runaway detection process. */ RunawayCleaner_RunawayCleanupDoneForSession(); } /* * Finally we are done with all critical cleanup, which includes releasing all our memory and * releasing our cleanup counter so that another session can be marked as runaway, if needed. * Now, we have some head room to actually record our usage. */ write_stderr("Logging memory usage because of runaway cleanup. Note, this is a post-cleanup logging and may be incomplete."); MemoryAccounting_SaveToLog(); MemoryContextStats(TopMemoryContext); }