{ *************************************************************************** Copyright (c) 2016-2020 Kike Pérez Unit : Quick.Pooling Description : Pooling objects Author : Kike Pérez Version : 1.9 Created : 28/02/2020 Modified : 29/02/2020 This file is part of QuickLib: https://github.com/exilon/QuickLib *************************************************************************** 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 Quick.Pooling; {$i QuickLib.inc} interface uses {$IFDEF DEBUG_OBJPOOL} Quick.Debug.Utils, {$ENDIF} System.SysUtils, System.SyncObjs, System.DateUtils, Quick.Commons, Quick.Threads; type IPoolItem = interface ['{D52E794B-FDC1-42C1-94BA-823DB74703E4}'] function Item : T; function GetRefCount : Integer; function GetItemIndex : Integer; function GetLastAccess: TDateTime; property RefCount: Integer read GetRefCount; property ItemIndex : Integer read GetItemIndex; property LastAccess: TDateTime read GetLastAccess; end; TCreateDelegator = reference to procedure(var aInstance : T); TPoolItem = class(TInterfacedObject,IPoolItem) private fItem : T; fItemIndex : Integer; fLastAccess : TDateTime; function GetRefCount : Integer; function GetLastAccess: TDateTime; function GetItemIndex : Integer; protected fLock : TCriticalSection; fSemaphore : TSemaphore; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; public constructor Create(aSemaphore : TSemaphore; aLock : TCriticalSection; aItemIndex : Integer; aCreateProc : TCreateDelegator); destructor Destroy; override; function Item : T; property RefCount: Integer read GetRefCount; property ItemIndex : Integer read GetItemIndex; property LastAccess: TDateTime read GetLastAccess; end; IObjectPool = interface ['{AA856DFB-AE8C-46FE-A107-034677010A58}'] function GetPoolSize: Integer; function Get : IPoolItem; property PoolSize : Integer read GetPoolSize; function TimeoutMs(aTimeout : Integer) : IObjectPool; function CreateDelegate(aCreateProc : TCreateDelegator) : IObjectPool; function AutoFreeIdleItemTimeMs(aIdleTimeMs : Integer) : IObjectPool; end; TObjectPool = class(TInterfacedObject,IObjectPool) private fPool : TArray>; fPoolSize : Integer; fWaitTimeoutMs : Integer; fLock : TCriticalSection; fDelegate : TCreateDelegator; fSemaphore : TSemaphore; fAutoFreeIdleItemTimeMs : Integer; fScheduler : TScheduledTasks; function GetPoolSize: Integer; procedure CreateScheduler; procedure CheckForIdleItems; public constructor Create(aPoolSize : Integer; aAutoFreeIdleItemTimeMs : Integer = 30000; aCreateProc : TCreateDelegator = nil); destructor Destroy; override; property PoolSize : Integer read GetPoolSize; function TimeoutMs(aTimeout : Integer) : IObjectPool; function CreateDelegate(aCreateProc : TCreateDelegator) : IObjectPool; function AutoFreeIdleItemTimeMs(aIdleTimeMs : Integer) : IObjectPool; function Get : IPoolItem; end; implementation { TObjectPool } function TObjectPool.AutoFreeIdleItemTimeMs(aIdleTimeMs: Integer): IObjectPool; begin Result := Self; fAutoFreeIdleItemTimeMs := aIdleTimeMs; end; constructor TObjectPool.Create(aPoolSize : Integer; aAutoFreeIdleItemTimeMs : Integer = 30000; aCreateProc : TCreateDelegator = nil); begin fLock := TCriticalSection.Create; fPoolSize := aPoolSize; fWaitTimeoutMs := 30000; fDelegate := aCreateProc; fAutoFreeIdleItemTimeMs := aAutoFreeIdleItemTimeMs; fSemaphore := TSemaphore.Create(nil,fPoolSize,fPoolSize,''); CreateScheduler; end; procedure TObjectPool.CreateScheduler; begin fScheduler := TScheduledTasks.Create; fScheduler.AddTask('IdleCleaner',[],True,procedure(task : ITask) begin CheckForIdleItems; end) .StartInSeconds(10).RepeatEvery(fAutoFreeIdleItemTimeMs,TTimeMeasure.tmMilliseconds); fScheduler.Start; end; procedure TObjectPool.CheckForIdleItems; var i : Integer; begin fLock.Enter; try for i := low(fPool) to High(fPool) do begin //check if item was not used for long time if (fPool[i] <> nil) and (fPool[i].RefCount = 1) and (MilliSecondsBetween(Now,fPool[i].LastAccess) > fAutoFreeIdleItemTimeMs) then begin fPool[i] := nil; end; end; finally fLock.Leave; end; end; function TObjectPool.CreateDelegate(aCreateProc: TCreateDelegator): IObjectPool; begin Result := Self; fDelegate := aCreateProc; end; destructor TObjectPool.Destroy; var i: Integer; begin fScheduler.Stop; fScheduler.Free; fLock.Enter; try for i := Low(fPool) to High(fPool) do fPool[i] := nil; SetLength(FPool,0); finally fLock.Leave; end; fLock.Free; fSemaphore.Free; inherited; end; function TObjectPool.Get: IPoolItem; var i : Integer; waitResult: TWaitResult; begin Result := nil; {$IFDEF DEBUG_OBJPOOL} TDebugger.Trace(Self,'Waiting for get idle Pool Item...'); {$ENDIF} waitResult := fSemaphore.WaitFor(fWaitTimeoutMs); if waitResult <> TWaitResult.wrSignaled then raise Exception.Create('Connection Pool Timeout: Cannot obtain a connection'); fLock.Enter; try if High(fPool) < fPoolSize then SetLength(fPool,High(fPool)+2); for i := Low(fPool) to High(fPool) do begin if fPool[i] = nil then begin fPool[i] := TPoolItem.Create(fSemaphore,fLock,i,fDelegate); {$IFDEF DEBUG_OBJPOOL} TDebugger.Trace(Self,'Create Pool Item: %d',[i]); {$ENDIF} Exit(fPool[i]); end; if fPool[i].RefCount = 1 then begin //writeln('get ' + i.ToString); {$IFDEF DEBUG_OBJPOOL} TDebugger.Trace(Self,'Get Idle Pool Item: %d',[i]); {$ENDIF} Exit(fPool[i]); end {$IFDEF DEBUG_OBJPOOL} else TDebugger.Trace(Self,'Pool Item: %d is busy (RefCount: %d)',[i,fPool[i].RefCount]); {$ENDIF} end; finally fLock.Leave; end; end; function TObjectPool.GetPoolSize: Integer; begin Result := fPoolSize; end; function TObjectPool.TimeoutMs(aTimeout: Integer): IObjectPool; begin fWaitTimeoutMs := aTimeout; end; { TPoolItem } function TPoolItem.Item: T; begin fLastAccess := Now(); Result := fItem; end; constructor TPoolItem.Create(aSemaphore : TSemaphore; aLock : TCriticalSection; aItemIndex : Integer; aCreateProc : TCreateDelegator); begin fLastAccess := Now(); fItemIndex := aItemIndex; if Assigned(aCreateProc) then aCreateProc(fItem) else fItem := T.Create; fLock := aLock; fSemaphore := aSemaphore; end; destructor TPoolItem.Destroy; begin if Assigned(fItem) then fItem.Free; inherited; end; function TPoolItem.GetItemIndex: Integer; begin Result := fItemIndex; end; function TPoolItem.GetLastAccess: TDateTime; begin Result := fLastAccess; end; function TPoolItem.GetRefCount: Integer; begin Result := FRefCount; end; function TPoolItem._AddRef: Integer; begin fLock.Enter; {$IFDEF DEBUG_OBJPOOL} TDebugger.Trace(Self,'Got Pool item'); {$ENDIF} try Inc(FRefCount); Result := FRefCount; finally fLock.Leave; end; end; function TPoolItem._Release: Integer; begin {$IFDEF DEBUG_OBJPOOL} TDebugger.Trace(Self,'Released Pool item'); {$ENDIF} try result:=AtomicDecrement(fRefCount); if Result = 0 then begin FreeAndNil(fItem); // The following is take from TInterfacedObject._Release() // Mark the refcount field so that any refcounting during destruction doesn't infinitely recurse. __MarkDestroying(Self); Destroy; end else fLastAccess := Now; finally if fRefCount = 1 then fSemaphore.Release; end; end; end.