/* * A shared method to test proper activation during IdleTracker_Init() * or a direct call to IdleTracker_ActivateProcess() during regular * activation of an idle process. */ static void CheckForActivation(void (*testFunc)(void)) { static EventVersion fakeCurrentVersion = 10; CurrentVersion = &fakeCurrentVersion; InitFakeSessionState(0 /* activeProcessCount */, CLEANUP_COUNTDOWN_BEFORE_RUNAWAY /* cleanupCountdown */, RunawayStatus_NotRunaway /* runawayStatus */, 1 /* pinCount */, 0 /* vmem */); EventVersion oldVersion = *CurrentVersion; activationVersion = 0; deactivationVersion = 0; assert_true(*CurrentVersion != activationVersion); assert_true(*CurrentVersion != deactivationVersion); /* * Set to false as we want to verify that it gets set to true * once the testFunc() call returns */ isProcessActive = false; assert_true(MySessionState->activeProcessCount == 0); testFunc(); assert_true(activationVersion == *CurrentVersion); assert_true(deactivationVersion == 0); assert_true(isProcessActive == true); assert_true(MySessionState->activeProcessCount == 1); }
/* * Checks if RunawayCleaner_RunawayCleanupDoneForProcess prevents multiple cleanup * for a single runaway event by properly updating beginCleanupRunawayVersion and * endCleanupRunawayVersion */ void test__RunawayCleaner_RunawayCleanupDoneForProcess__PreventsDuplicateCleanup(void **state) { InitFakeSessionState(2 /* activeProcessCount */, 2 /* cleanupCountdown */, RunawayStatus_PrimaryRunawaySession /* runawayStatus */, CLEANUP_COUNTDOWN /* pinCount */, 12345 /* vmem */); static fakeLatestRunawayVersion = 10; latestRunawayVersion = &fakeLatestRunawayVersion; *latestRunawayVersion = 10; /* * Some imaginary cleanup begin/end event version. The idea is to ensure * that once the RunawayCleaner_RunawayCleanupDoneForProcess call returns * we will have both set to latestRunawayVersion */ beginCleanupRunawayVersion = *latestRunawayVersion; endCleanupRunawayVersion = 0; /* Make sure the cleanup goes through */ vmemTrackerInited = true; isProcessActive = true; RunawayCleaner_RunawayCleanupDoneForProcess(false /* ignoredCleanup */); /* cleanupCountdown should be adjusted */ assert_true(MySessionState->cleanupCountdown == CLEANUP_COUNTDOWN - 1); assert_true(beginCleanupRunawayVersion == endCleanupRunawayVersion); assert_true(beginCleanupRunawayVersion == *latestRunawayVersion); /* Second call shouldn't change anything */ RunawayCleaner_RunawayCleanupDoneForProcess(false /* ignoredCleanup */); /* cleanupCountdown is unchanged */ assert_true(MySessionState->cleanupCountdown == CLEANUP_COUNTDOWN - 1); }
/* * Checks if RunawayCleaner_RunawayCleanupDoneForProcess reactivates the runaway detector * once all the processes of the runaway session are done cleaning */ void test__RunawayCleaner_RunawayCleanupDoneForProcess__ReactivatesRunawayDetection(void **state) { InitFakeSessionState(2 /* activeProcessCount */, 2 /* cleanupCountdown */, RunawayStatus_PrimaryRunawaySession /* runawayStatus */, 2 /* pinCount */, 12345 /* vmem */); static EventVersion fakeLatestRunawayVersion = 10; latestRunawayVersion = &fakeLatestRunawayVersion; /* * Set beginCleanupRunawayVersion to latestRunawayVersion and endCleanupRunawayVersion * to a smaller value to simulate an ongoing cleanup */ beginCleanupRunawayVersion = *latestRunawayVersion; endCleanupRunawayVersion = 1; /* Valid isRunawayDetector is necessary for Assert */ static uint32 fakeIsRunawayDetector = 1; isRunawayDetector = &fakeIsRunawayDetector; /* Just an active process that never became idle */ activationVersion = 0; deactivationVersion = 0; isProcessActive = true; /* Make sure the cleanup goes through */ vmemTrackerInited = true; RunawayCleaner_RunawayCleanupDoneForProcess(false /* ignoredCleanup */); /* The cleanupCountdown must be decremented as we cleaned up */ assert_true(MySessionState->cleanupCountdown == 1); /* We updated the endCleanupRunawayVersion to indicate that we finished cleanup */ assert_true(endCleanupRunawayVersion == beginCleanupRunawayVersion); /* The runaway detector promotion should be disabled as we still have 1 QE unclean */ assert_true(*isRunawayDetector == 1); /* * Fake a ongoing cleanup by making endCleanupRunawayVersion < beginCleanupRunawayVersion * so that we can execute cleanup one more time, marking all QEs clean */ endCleanupRunawayVersion = 1; /* * cleanupCountdown should reach 0, and immediately afterwards should be set to * CLEANUP_COUNTDOWN_BEFORE_RUNAWAY */ RunawayCleaner_RunawayCleanupDoneForProcess(false /* ignoredCleanup */); assert_true(MySessionState->cleanupCountdown == CLEANUP_COUNTDOWN_BEFORE_RUNAWAY); /* Runaway detector should be re-enabled */ assert_true(*isRunawayDetector == 0); }
/* * A shared method to test proper deactivation during IdleTracker_Shutdown() * or a direct call to IdleTracker_DeactivateProcess() during regular * deactivation of an active process when a proper cleanup was done, throwing * and elog(ERROR,...) and therefore the call would never return to the calling * function */ static void CheckForDeactivationWithProperCleanup(void (*testFunc)(void)) { static EventVersion fakeCurrentVersion = 10; CurrentVersion = &fakeCurrentVersion; InitFakeSessionState(1 /* activeProcessCount */, CLEANUP_COUNTDOWN_BEFORE_RUNAWAY /* cleanupCountdown */, RunawayStatus_NotRunaway /* runawayStatus */, 1 /* pinCount */, 0 /* vmem */); EventVersion oldVersion = *CurrentVersion; /* Ensure we have a pending runaway event */ EventVersion fakeLatestRunawayVersion = *CurrentVersion - 1; latestRunawayVersion = &fakeLatestRunawayVersion; activationVersion = 0; deactivationVersion = 0; assert_true(*CurrentVersion != activationVersion); assert_true(*CurrentVersion != deactivationVersion); /* * Set to true as we want to verify that it gets set to false * once the testFunc() call returns */ isProcessActive = true; assert_true(MySessionState->activeProcessCount == 1); /* * Deactivation must call RunawayCleaner_StartCleanup before finishing deactivation * to check for cleanup requirement for any pending runaway event. The mocked method * is throwing an exception here, which would block execution of code following the call * site */ will_be_called_with_sideeffect(RunawayCleaner_StartCleanup, &_ExceptionalCondition, NULL); PG_TRY(); { testFunc(); assert_false("Expected an exception"); } PG_CATCH(); { } PG_END_TRY(); assert_true(activationVersion == 0); assert_true(deactivationVersion == *CurrentVersion); assert_true(isProcessActive == false); assert_true(MySessionState->activeProcessCount == 0); }
/* * Checks if RunawayCleaner_StartCleanup() does not start cleanup if * the current session is not a runaway */ void test__RunawayCleaner_StartCleanup__IgnoresNonRunaway(void **state) { InitFakeSessionState(2 /* activeProcessCount */, CLEANUP_COUNTDOWN_BEFORE_RUNAWAY /* cleanupCountdown */, RunawayStatus_NotRunaway /* runawayStatus */, 2 /* pinCount */, 0 /* vmem */); static fakeLatestRunawayVersion = 10; latestRunawayVersion = &fakeLatestRunawayVersion; beginCleanupRunawayVersion = 0; RunawayCleaner_StartCleanup(); /* Cleanup shouldn't have begun */ assert_true(beginCleanupRunawayVersion != *latestRunawayVersion); }
/* * Checks if RunawayCleaner_StartCleanup() does not execute a duplicate * cleanup for the same runaway event that it already started cleaning up */ void test__RunawayCleaner_StartCleanup__IgnoresDuplicateCleanup(void **state) { InitFakeSessionState(2 /* activeProcessCount */, 2 /* cleanupCountdown */, RunawayStatus_PrimaryRunawaySession /* runawayStatus */, 2 /* pinCount */, 0 /* vmem */); static fakeLatestRunawayVersion = 10; latestRunawayVersion = &fakeLatestRunawayVersion; beginCleanupRunawayVersion = *latestRunawayVersion; /* * As we are not providing IsCommitInProgress, the call itself verifies * that we are not attempting any cleanup */ RunawayCleaner_StartCleanup(); }
/* * Checks if RunawayCleaner_RunawayCleanupDoneForProcess reactivates a process * if the deactivation process triggers cleanup for a pending runaway event */ void test__RunawayCleaner_RunawayCleanupDoneForProcess__UndoDeactivation(void **state) { InitFakeSessionState(2 /* activeProcessCount */, 2 /* cleanupCountdown */, RunawayStatus_PrimaryRunawaySession /* runawayStatus */, 2 /* pinCount */, 12345 /* vmem */); static fakeLatestRunawayVersion = 10; latestRunawayVersion = &fakeLatestRunawayVersion; /* * Set beginCleanupRunawayVersion to latestRunawayVersion and endCleanupRunawayVersion * to a smaller value to simulate an ongoing cleanup */ beginCleanupRunawayVersion = *latestRunawayVersion; endCleanupRunawayVersion = 1; /* Valid isRunawayDetector is necessary for Assert */ static uint32 fakeIsRunawayDetector = 1; isRunawayDetector = &fakeIsRunawayDetector; /* Make sure we became idle after a pending runaway event */ activationVersion = 1; deactivationVersion = *latestRunawayVersion + 1; /* Make sure the cleanup goes through */ vmemTrackerInited = true; isProcessActive = false; /* We must undo the idle state */ will_be_called(IdleTracker_ActivateProcess); RunawayCleaner_RunawayCleanupDoneForProcess(false /* ignoredCleanup */); /* The cleanupCountdown must be decremented as we cleaned up */ assert_true(MySessionState->cleanupCountdown == 1); /* We updated the endCleanupRunawayVersion to indicate that we finished cleanup */ assert_true(endCleanupRunawayVersion == beginCleanupRunawayVersion); }
/* * Checks if RunawayCleaner_RunawayCleanupDoneForSession reactivates the runaway detector */ void test__RunawayCleaner_RunawayCleanupDoneForSession__ResetsRunawayFlagAndReactivateRunawayDetector(void **state) { InitFakeSessionState(2 /* activeProcessCount */, 2 /* cleanupCountdown */, RunawayStatus_PrimaryRunawaySession /* runawayStatus */, 2 /* pinCount */, 12345 /* vmem */); static EventVersion fakeLatestRunawayVersion = 10; latestRunawayVersion = &fakeLatestRunawayVersion; /* * Satisfy asserts */ beginCleanupRunawayVersion = *latestRunawayVersion; endCleanupRunawayVersion = beginCleanupRunawayVersion; MySessionState->cleanupCountdown = CLEANUP_COUNTDOWN_BEFORE_RUNAWAY; *isRunawayDetector = 1; MySessionState->runawayStatus = RunawayStatus_PrimaryRunawaySession; RunawayCleaner_RunawayCleanupDoneForSession(); assert_true(MySessionState->runawayStatus == RunawayStatus_NotRunaway); /* Runaway detector should be re-enabled */ assert_true(*isRunawayDetector == 0); }
/* * Checks if RunawayCleaner_RunawayCleanupDoneForProcess ignores duplicate cleanup * for a single runaway event */ void test__RunawayCleaner_RunawayCleanupDoneForProcess__IgnoresDuplicateCalls(void **state) { InitFakeSessionState(2 /* activeProcessCount */, 2 /* cleanupCountdown */, RunawayStatus_PrimaryRunawaySession /* runawayStatus */, 2 /* pinCount */, 12345 /* vmem */); static fakeLatestRunawayVersion = 10; latestRunawayVersion = &fakeLatestRunawayVersion; /* * Set beginCleanupRunawayVersion and endCleanupRunawayVersion to * latestRunawayVersion which should make the function call a no-op */ beginCleanupRunawayVersion = *latestRunawayVersion; endCleanupRunawayVersion = *latestRunawayVersion; /* Make sure the cleanup goes through */ vmemTrackerInited = true; isProcessActive = true; RunawayCleaner_RunawayCleanupDoneForProcess(false /* ignoredCleanup */); /* Nothing got cleaned */ assert_true(MySessionState->cleanupCountdown == 2); }
/* * Checks if RunawayCleaner_StartCleanup() ignores cleanup if interrupts are held off */ void test__RunawayCleaner_StartCleanup__IgnoresCleanupInHoldoffInterrupt(void **state) { InitFakeSessionState(2 /* activeProcessCount */, 2 /* cleanupCountdown */, RunawayStatus_PrimaryRunawaySession /* runawayStatus */, 2 /* pinCount */, 12345 /* vmem */); static EventVersion fakeLatestRunawayVersion = 10; latestRunawayVersion = &fakeLatestRunawayVersion; /* * Set beginCleanupRunawayVersion to less than *latestRunawayVersino * to trigger a cleanup */ beginCleanupRunawayVersion = 1; endCleanupRunawayVersion = 1; /* Make sure the cleanup goes through */ vmemTrackerInited = true; isProcessActive = true; CritSectionCount = 0; InterruptHoldoffCount = 1; RunawayCleaner_StartCleanup(); assert_true(beginCleanupRunawayVersion == *latestRunawayVersion); /* Cleanup is done, without ever throwing an ERROR */ assert_true(endCleanupRunawayVersion == beginCleanupRunawayVersion); /* * cleanupCountdown is decremented by 1 as there was no error, and therefore * the cleanup is done within the same call of RunawayCleaner_StartCleanup */ assert_true(MySessionState->cleanupCountdown == 1); InterruptHoldoffCount = 0; }
/* * Checks if RunawayCleaner_RunawayCleanupDoneForProcess() ignores cleanupCountdown * if optional cleanup */ void test__RunawayCleaner_RunawayCleanupDoneForProcess__IgnoresCleanupIfNotRequired(void **state) { #define CLEANUP_COUNTDOWN 2 InitFakeSessionState(2 /* activeProcessCount */, CLEANUP_COUNTDOWN /* cleanupCountdown */, RunawayStatus_PrimaryRunawaySession /* runawayStatus */, 2 /* pinCount */, 12345 /* vmem */); static EventVersion fakeLatestRunawayVersion = 10; latestRunawayVersion = &fakeLatestRunawayVersion; *latestRunawayVersion = 10; /* * Set beginCleanupRunawayVersion to less than *latestRunawayVersino * to trigger a cleanup */ beginCleanupRunawayVersion = 1; endCleanupRunawayVersion = 1; /* Make sure the cleanup is not ignored for vmem initialization */ vmemTrackerInited = true; /* Simulate a deactivation before the runaway */ deactivationVersion = *latestRunawayVersion - 1; activationVersion = *latestRunawayVersion - 1; isProcessActive = false; CritSectionCount = 0; InterruptHoldoffCount = 0; RunawayCleaner_StartCleanup(); /* The cleanup shouldn't even start as the QE was deactivated at the time of the runaway*/ assert_true(beginCleanupRunawayVersion == 1); assert_true(endCleanupRunawayVersion == 1); /* * cleanupCountdown should not be decremented as this was an optional cleanup */ assert_true(MySessionState->cleanupCountdown == CLEANUP_COUNTDOWN); assert_true(MySessionState->runawayStatus == RunawayStatus_PrimaryRunawaySession); /* * Now simulate a scenario where the we activated but the runaway happened * before the activation */ beginCleanupRunawayVersion = 2; endCleanupRunawayVersion = 2; /* Another runaway happened after the last cleanup */ *latestRunawayVersion = beginCleanupRunawayVersion + 2; activationVersion = 5; deactivationVersion = 3; isProcessActive = true; RunawayCleaner_StartCleanup(); /* The cleanup shouldn't even start as the runaway event happened before the QE became active */ assert_true(beginCleanupRunawayVersion == 2); assert_true(endCleanupRunawayVersion == 2); /* * cleanupCountdown should not be decremented as this was an optional cleanup */ assert_true(MySessionState->cleanupCountdown == CLEANUP_COUNTDOWN); assert_true(MySessionState->runawayStatus == RunawayStatus_PrimaryRunawaySession); }
/* * Checks if RunawayCleaner_StartCleanup() starts the cleanup process if * all conditions are met (i.e., no commit is in progress and vmem tracker * is initialized) */ void test__RunawayCleaner_StartCleanup__StartsCleanupIfPossible(void **state) { InitFakeSessionState(2 /* activeProcessCount */, 2 /* cleanupCountdown */, RunawayStatus_PrimaryRunawaySession /* runawayStatus */, 2 /* pinCount */, 12345 /* vmem */); static fakeLatestRunawayVersion = 10; latestRunawayVersion = &fakeLatestRunawayVersion; *latestRunawayVersion = 10; /* * Set beginCleanupRunawayVersion to less than *latestRunawayVersion * to trigger a cleanup */ beginCleanupRunawayVersion = 1; endCleanupRunawayVersion = 1; isProcessActive = true; /* Make sure the cleanup goes through */ vmemTrackerInited = true; CritSectionCount = 0; InterruptHoldoffCount = 0; /* We need a valid gp_command_count to execute cleanup */ gp_command_count = 1; will_return(superuser, false); #ifdef FAULT_INJECTOR expect_value(FaultInjector_InjectFaultIfSet, identifier, RunawayCleanup); expect_value(FaultInjector_InjectFaultIfSet, ddlStatement, DDLNotSpecified); expect_value(FaultInjector_InjectFaultIfSet, databaseName, ""); expect_value(FaultInjector_InjectFaultIfSet, tableName, ""); will_be_called(FaultInjector_InjectFaultIfSet); #endif EXPECT_EREPORT(ERROR); PG_TRY(); { RunawayCleaner_StartCleanup(); assert_false("Cleanup didn't throw error"); } PG_CATCH(); { } PG_END_TRY(); assert_true(beginCleanupRunawayVersion == *latestRunawayVersion); /* We should not finish the cleanup as we errored out */ assert_true(endCleanupRunawayVersion == 1); /* cleanupCountdown shouldn't change as we haven't finished cleanup */ assert_true(MySessionState->cleanupCountdown == 2); /* * If we call RunawayCleaner_StartCleanup again for the same runaway event, * it should be a noop, therefore requiring no "will_be_called" setup */ RunawayCleaner_StartCleanup(); }
/* * A shared method to test proper deactivation during IdleTracker_Shutdown() * or a direct call to IdleTracker_DeactivateProcess() during regular * deactivation of an active process when a proper cleanup was not possible * (e.g., a transaction is in progress). */ static void CheckForDeactivationWithoutCleanup(void (*testFunc)(void)) { static EventVersion fakeCurrentVersion = 10; CurrentVersion = &fakeCurrentVersion; InitFakeSessionState(1 /* activeProcessCount */, CLEANUP_COUNTDOWN_BEFORE_RUNAWAY /* cleanupCountdown */, RunawayStatus_NotRunaway /* runawayStatus */, 1 /* pinCount */, 0 /* vmem */); EventVersion oldVersion = *CurrentVersion; /* Ensure we have a pending runaway event */ EventVersion fakeLatestRunawayVersion = *CurrentVersion - 1; latestRunawayVersion = &fakeLatestRunawayVersion; activationVersion = 0; deactivationVersion = 0; assert_true(*CurrentVersion != activationVersion); assert_true(*CurrentVersion != deactivationVersion); /* * Set to true as we want to verify that it gets set to false * once the testFunc() call returns */ isProcessActive = true; assert_true(MySessionState->activeProcessCount == 1); /* * Deactivation must call RunawayCleaner_StartCleanup before finishing deactivation * to check for cleanup requirement for any pending runaway event. The method is * supposed to throw an exception, but for this test we are mocking the function * without side effect. I.e., the function behaves as if a proper cleanup is not * possible */ will_be_called(RunawayCleaner_StartCleanup); #ifdef USE_ASSERT_CHECKING will_return(RunawayCleaner_IsCleanupInProgress, true); /* * Expecting an exception as we are indicating an ongoing cleanup * and yet returning to IdleTracker_DactivateProcess */ EXPECT_EXCEPTION(); #endif PG_TRY(); { testFunc(); #ifdef USE_ASSERT_CHECKING assert_false("Expected an assertion failure"); #endif } PG_CATCH(); { } PG_END_TRY(); assert_true(activationVersion == 0); assert_true(deactivationVersion == *CurrentVersion); assert_true(isProcessActive == false); assert_true(MySessionState->activeProcessCount == 0); }
/* * A shared method to test that the IdleTracker_DeactivateProcess ignores * deactivation for an already idle process when the proc_exit_inprogress * is set to true */ static void PreventDuplicateDeactivationDuringProcExit(void (*testFunc)(void)) { static EventVersion fakeCurrentVersion = 10; CurrentVersion = &fakeCurrentVersion; InitFakeSessionState(1 /* activeProcessCount */, CLEANUP_COUNTDOWN_BEFORE_RUNAWAY /* cleanupCountdown */, RunawayStatus_NotRunaway /* runawayStatus */, 1 /* pinCount */, 0 /* vmem */); EventVersion oldVersion = *CurrentVersion; /* Ensure we have a pending runaway event */ EventVersion fakeLatestRunawayVersion = *CurrentVersion - 1; latestRunawayVersion = &fakeLatestRunawayVersion; activationVersion = 0; deactivationVersion = 0; assert_true(*CurrentVersion != activationVersion); assert_true(*CurrentVersion != deactivationVersion); /* * Set to false to mark the process as deactivated */ isProcessActive = false; assert_true(MySessionState->activeProcessCount == 1); /* * Setting proc_exit_inprogress to true means we won't try to * deactivate an already idle process */ proc_exit_inprogress = true; /* Interrupts should be held off during proc_exit_inprogress*/ InterruptHoldoffCount = 1; /* Now the deactivation should succeed as we have held off interrupts */ testFunc(); InterruptHoldoffCount = 0; assert_true(activationVersion == deactivationVersion); assert_true(deactivationVersion != *CurrentVersion); assert_true(isProcessActive == false); /* We haven't reduced the activeProcessCount */ assert_true(MySessionState->activeProcessCount == 1); #ifdef USE_ASSERT_CHECKING /* * Now test that the testFunc fails assert if we try to deactivate an already * idle process during a normal execution (i.e., not proc_exit_inprogress) */ proc_exit_inprogress = false; EXPECT_EXCEPTION(); PG_TRY(); { testFunc(); assert_false("Expected assertion failure"); } PG_CATCH(); { } PG_END_TRY(); #endif }