// *************************************************************************** // // Delphi MVC Framework // // Copyright (c) 2010-2024 Daniele Teti and the DMVCFramework Team // // https://github.com/danieleteti/delphimvcframework // // The Initial Developer of the Original Code is Vivaticket S.p.A. https://www.vivaticket.com/ // The code has been fully donated to the DMVCFramework community without any charge nor rights. // // *************************************************************************** // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // *************************************************************************** unit MVCFramework.ObjectPool; interface uses System.SysUtils, System.Classes, System.Generics.Collections, System.DateUtils; const AVG_SAMPLES_COUNT = 10; type EObjectPool = class(Exception) end; IObjectPool = interface ['{0E79863D-D6F9-4426-9D80-F4C215233582}'] function GetFromPool(const RaiseExceptionIfNotAvailable: Boolean = False): T; procedure ReleaseToPool(const Obj: T); function Size: UInt32; end; TCleanupThread = class; TObjectPool = class(TInterfacedObject, IObjectPool) private fFactory: TFunc; fMaxSize: Integer; fPool: TStack; fSize: Integer; fShrinkTargetSize: Integer; fShrinkTriggerSize: Integer; fCleanupThread: TCleanupThread; fLastGetFromPool: TDateTime; fOnResetState: TProc; protected procedure Lock; procedure UnLock; procedure ShrinkPoolTo(const TargetSize: Integer); public constructor Create(MaxSize: Integer; ShrinkTriggerSize, ShrinkTargetSize: Integer; const Factory: TFunc = nil); destructor Destroy; override; function GetFromPool(const RaiseExceptionIfNotAvailable: Boolean = False): T; procedure ReleaseToPool(const Obj: T); function Size: UInt32; property OnResetState: TProc read fOnResetState write fOnResetState; end; TCleanupThread = class(TThread) private fObjectPool: TObjectPool; type TPoolSizeSamples = array [0..AVG_SAMPLES_COUNT-1] of Integer; function GetAveragePoolSize(var SizeSamples: TPoolSizeSamples): Integer; protected procedure Execute; override; public constructor Create(ObjectPool: TObjectPool); end; TPoolFactory = class public class function CreatePool(MaxSize: Integer; ShrinkTriggerSize, ShrinkTargetSize: Integer; const Factory: TFunc = nil): IObjectPool; class function CreateUnlimitedPool(ShrinkTriggerSize, ShrinkTargetSize: Integer; const Factory: TFunc = nil): IObjectPool; end; var GObjectPoolSamplingIntervalMS: UInt32 = 10000; implementation { TObjectPool } constructor TObjectPool.Create(MaxSize: Integer; ShrinkTriggerSize, ShrinkTargetSize: Integer; const Factory: TFunc); begin inherited Create; fOnResetState := nil; fLastGetFromPool := 0; fFactory := Factory; fMaxSize := MaxSize; fShrinkTargetSize := ShrinkTargetSize; fShrinkTriggerSize := ShrinkTriggerSize; fPool := TStack.Create; fCleanupThread := nil; if fShrinkTriggerSize > 0 then begin fCleanupThread := TCleanupThread.Create(Self); end; end; destructor TObjectPool.Destroy; begin if Assigned(fCleanupThread) then begin fCleanupThread.Terminate; fCleanupThread.Free; end; while fPool.Count > 0 do fPool.Pop.Free; FreeAndNil(fPool); inherited; end; function TObjectPool.GetFromPool(const RaiseExceptionIfNotAvailable: Boolean): T; begin MonitorEnter(Self); try fLastGetFromPool := Now(); if fPool.Count > 0 then begin Exit(fPool.Pop); end; if (fMaxSize > 0) and (fSize >= fMaxSize) then begin if RaiseExceptionIfNotAvailable then raise EObjectPool.CreateFmt('Pool cannot provide an instance of %s', [T.ClassName]); Exit(nil); end; if Assigned(fFactory) then begin Result := fFactory() end else begin Result := T.Create; end; Inc(fSize); finally MonitorExit(Self); end; end; procedure TObjectPool.Lock; begin MonitorEnter(Self); end; procedure TObjectPool.ReleaseToPool(const Obj: T); begin if Assigned(fOnResetState) then begin fOnResetState(Obj); end; MonitorEnter(Self); try fPool.Push(Obj); finally MonitorExit(Self); end; end; procedure TObjectPool.ShrinkPoolTo(const TargetSize: Integer); begin MonitorEnter(Self); try while fSize > TargetSize do begin fPool.Pop.Free; Dec(fSize); end; finally MonitorExit(Self); end; end; function TObjectPool.Size: UInt32; begin MonitorEnter(Self); try Result := fPool.Count; finally MonitorExit(Self); end; end; procedure TObjectPool.UnLock; begin MonitorExit(Self); end; constructor TCleanupThread.Create(ObjectPool: TObjectPool); begin fObjectPool := ObjectPool; inherited Create(False); end; procedure TCleanupThread.Execute; var lAvgSize: TPoolSizeSamples; lArrIndex: Integer; lSampleTick: Integer; begin lSampleTick := 0; while not Terminated do begin Inc(lSampleTick); lArrIndex := lSampleTick mod AVG_SAMPLES_COUNT; lAvgSize[lArrIndex] := fObjectPool.Size; if (lAvgSize[lArrIndex] > 0) and (GetAveragePoolSize(lAvgSize) >= fObjectPool.fShrinkTriggerSize) then begin fObjectPool.Lock; try fObjectPool.ShrinkPoolTo(fObjectPool.fShrinkTargetSize); FillChar(lAvgSize, SizeOf(lAvgSize), 0); finally fObjectPool.UnLock; end; end else begin Sleep(GObjectPoolSamplingIntervalMS); if lSampleTick = MaxInt then begin lSampleTick := 0; end; end; end; end; function TCleanupThread.GetAveragePoolSize( var SizeSamples: TPoolSizeSamples): Integer; begin Result := 0; for var I := Low(TPoolSizeSamples) to High(TPoolSizeSamples) do begin Inc(Result, SizeSamples[I]); end; Result := Result div Length(SizeSamples); end; { TPoolFactory } class function TPoolFactory.CreatePool(MaxSize, ShrinkTriggerSize, ShrinkTargetSize: Integer; const Factory: TFunc): IObjectPool; begin Result := TObjectPool.Create(MaxSize, ShrinkTriggerSize, ShrinkTargetSize, Factory); end; class function TPoolFactory.CreateUnlimitedPool(ShrinkTriggerSize, ShrinkTargetSize: Integer; const Factory: TFunc): IObjectPool; begin Result := CreatePool(0, ShrinkTriggerSize, ShrinkTargetSize, Factory); end; end.