import { strict as assert } from "assert" ; import { ContainerRuntimeFactoryWithDefaultDataStore , DataObjectFactory , } from "@fluidframework/aqueduct" ; import { TelemetryNullLogger } from "@fluidframework/common-utils" ; import { ContainerRuntime , IContainerRuntimeOptions } from "@fluidframework/container-runtime" ; import { Container } from "@fluidframework/container-loader" ; import { requestFluidObject } from "@fluidframework/runtime-utils" ; import { MockLogger } from "@fluidframework/telemetry-utils" ; import { ITestObjectProvider } from "@fluidframework/test-utils" ; import { describeNoCompat } from "@fluidframework/test-version-utils" ; import { TestDataObject } from "./mockSummarizerClient" ; /**
 * Validates this scenario: When a data store becomes inactive (has been unreferenced for a given amount of time),
 * using that data store results in an error telemetry.
 */ describeNoCompat ( "GC inactive data store tests" , ( getTestObjectProvider ) => { const dataObjectFactory = new DataObjectFactory ( "TestDataObject" , TestDataObject , [ ] , [ ] ) ; const deleteTimeoutMs = 2000 ; const runtimeOptions = { summaryOptions : { disableSummaries : true } , gcOptions : { gcAllowed : true , deleteTimeoutMs } } ; const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore ( dataObjectFactory , [ [ dataObjectFactory . type , Promise . resolve ( dataObjectFactory ) ] , ] , undefined , undefined , runtimeOptions ) ; const summaryLogger = new TelemetryNullLogger ( ) ; const inactiveObjectRevivedEvent = "fluid:telemetry:ContainerRuntime:GarbageCollector:inactiveObjectRevived" ; const inactiveObjectChangedEvent = "fluid:telemetry:ContainerRuntime:GarbageCollector:inactiveObjectChanged" ; let provider ; let containerRuntime ; let defaultDataStore ; let mockLogger ; const createContainer = async ( logger ) => provider . createContainer ( runtimeFactory , { logger } ) ; /** Waits for the given amount of time to be passed. */ async function waitForTimeout ( timeout ) { await new Promise < void > ( ( resolve ) => { setTimeout ( resolve , timeout ) ; } ) ; } /** Validates that none of the inactive events have been logged since the last run. */ function validateNoInactiveEvents ( ) { assert ( ! mockLogger . matchAnyEvent ( [ { eventName : inactiveObjectRevivedEvent } , { eventName : inactiveObjectChangedEvent } , ] ) , "inactive object events should not have been logged" ) ; } before ( function ( ) { provider = getTestObjectProvider ( ) ; if ( provider . driver . type !== "local" ) { this . skip ( ) ; } } ) ; beforeEach ( async ( ) => { mockLogger = new MockLogger ( ) ; const container = await createContainer ( mockLogger ) as Container ; defaultDataStore = await requestFluidObject < TestDataObject > ( container , "/" ) ; containerRuntime = defaultDataStore . containerRuntime ; } ) ; it ( "can generate events when unreferenced data store is accessed after it's inactive" , async ( ) => { const dataStore1 = await dataObjectFactory . createInstance ( containerRuntime ) ; defaultDataStore . _root . set ( "dataStore1" , dataStore1 . handle ) ; await provider . ensureSynchronized ( ) ; await containerRuntime . summarize ( { runGC : true , fullTree : true , trackState : false , summaryLogger } ) ; validateNoInactiveEvents ( ) ; defaultDataStore . _root . delete ( "dataStore1" ) ; await provider . ensureSynchronized ( ) ; await containerRuntime . summarize ( { runGC : true , fullTree : true , trackState : false , summaryLogger } ) ; validateNoInactiveEvents ( ) ; await waitForTimeout ( 3000 ) ; dataStore1 . _root . set ( "key" , "value" ) ; await provider . ensureSynchronized ( ) ; assert ( mockLogger . matchEvents ( [ { eventName : inactiveObjectChangedEvent , timeout : deleteTimeoutMs , id : `/${ dataStore1 . id }` } , ] ) , "inactiveObjectChanged event not generated as expected" ) ; dataStore1 . _root . set ( "key2" , "value2" ) ; await provider . ensureSynchronized ( ) ; validateNoInactiveEvents ( ) ; defaultDataStore . _root . set ( "dataStore1" , dataStore1 . handle ) ; await provider . ensureSynchronized ( ) ; await containerRuntime . summarize ( { runGC : true , fullTree : true , trackState : false , summaryLogger } ) ; assert ( mockLogger . matchEvents ( [ { eventName : inactiveObjectRevivedEvent , timeout : deleteTimeoutMs , id : `/${ dataStore1 . id }` } , ] ) , "inactiveObjectRevived event not generated as expected" ) ; } ) . timeout ( 10000 ) ; } ) ;