Quick.Pooling.pas 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. { ***************************************************************************
  2. Copyright (c) 2016-2020 Kike Pérez
  3. Unit : Quick.Pooling
  4. Description : Pooling objects
  5. Author : Kike Pérez
  6. Version : 1.9
  7. Created : 28/02/2020
  8. Modified : 29/02/2020
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.Pooling;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. System.SysUtils,
  26. System.SyncObjs,
  27. System.DateUtils,
  28. Quick.Commons,
  29. Quick.Threads;
  30. type
  31. IPoolItem<T : class, constructor> = interface
  32. ['{D52E794B-FDC1-42C1-94BA-823DB74703E4}']
  33. function Item : T;
  34. function GetRefCount : Integer;
  35. function GetItemIndex : Integer;
  36. function GetLastAccess: TDateTime;
  37. property RefCount: Integer read GetRefCount;
  38. property ItemIndex : Integer read GetItemIndex;
  39. property LastAccess: TDateTime read GetLastAccess;
  40. end;
  41. TCreateDelegator<T> = reference to procedure(var aInstance : T);
  42. TPoolItem<T : class, constructor> = class(TInterfacedObject,IPoolItem<T>)
  43. private
  44. fItem : T;
  45. fItemIndex : Integer;
  46. fLastAccess : TDateTime;
  47. function GetRefCount : Integer;
  48. function GetLastAccess: TDateTime;
  49. function GetItemIndex : Integer;
  50. protected
  51. fLock : TCriticalSection;
  52. fSemaphore : TSemaphore;
  53. function _AddRef: Integer; stdcall;
  54. function _Release: Integer; stdcall;
  55. public
  56. constructor Create(aSemaphore : TSemaphore; aLock : TCriticalSection; aItemIndex : Integer; aCreateProc : TCreateDelegator<T>);
  57. destructor Destroy; override;
  58. function Item : T;
  59. property RefCount: Integer read GetRefCount;
  60. property ItemIndex : Integer read GetItemIndex;
  61. property LastAccess: TDateTime read GetLastAccess;
  62. end;
  63. IObjectPool<T : class, constructor> = interface
  64. ['{AA856DFB-AE8C-46FE-A107-034677010A58}']
  65. function GetPoolSize: Integer;
  66. function Get : IPoolItem<T>;
  67. property PoolSize : Integer read GetPoolSize;
  68. function TimeoutMs(aTimeout : Integer) : IObjectPool<T>;
  69. function CreateDelegate(aCreateProc : TCreateDelegator<T>) : IObjectPool<T>;
  70. function AutoFreeIdleItemTimeMs(aIdleTimeMs : Integer) : IObjectPool<T>;
  71. end;
  72. TObjectPool<T : class, constructor> = class(TInterfacedObject,IObjectPool<T>)
  73. private
  74. fPool : TArray<IPoolItem<T>>;
  75. fPoolSize : Integer;
  76. fWaitTimeoutMs : Integer;
  77. fLock : TCriticalSection;
  78. fDelegate : TCreateDelegator<T>;
  79. fSemaphore : TSemaphore;
  80. fAutoFreeIdleItemTimeMs : Integer;
  81. fScheduler : TScheduledTasks;
  82. function GetPoolSize: Integer;
  83. procedure CreateScheduler;
  84. procedure CheckForIdleItems;
  85. public
  86. constructor Create(aPoolSize : Integer; aAutoFreeIdleItemTimeMs : Integer = 30000; aCreateProc : TCreateDelegator<T> = nil);
  87. destructor Destroy; override;
  88. property PoolSize : Integer read GetPoolSize;
  89. function TimeoutMs(aTimeout : Integer) : IObjectPool<T>;
  90. function CreateDelegate(aCreateProc : TCreateDelegator<T>) : IObjectPool<T>;
  91. function AutoFreeIdleItemTimeMs(aIdleTimeMs : Integer) : IObjectPool<T>;
  92. function Get : IPoolItem<T>;
  93. end;
  94. implementation
  95. { TObjectPool<T> }
  96. function TObjectPool<T>.AutoFreeIdleItemTimeMs(aIdleTimeMs: Integer): IObjectPool<T>;
  97. begin
  98. fAutoFreeIdleItemTimeMs := aIdleTimeMs;
  99. end;
  100. constructor TObjectPool<T>.Create(aPoolSize : Integer; aAutoFreeIdleItemTimeMs : Integer = 30000; aCreateProc : TCreateDelegator<T> = nil);
  101. begin
  102. fLock := TCriticalSection.Create;
  103. fPoolSize := aPoolSize;
  104. fWaitTimeoutMs := 30000;
  105. fDelegate := aCreateProc;
  106. fAutoFreeIdleItemTimeMs := aAutoFreeIdleItemTimeMs;
  107. fSemaphore := TSemaphore.Create(nil,fPoolSize,fPoolSize,'');
  108. CreateScheduler;
  109. end;
  110. procedure TObjectPool<T>.CreateScheduler;
  111. begin
  112. fScheduler := TScheduledTasks.Create;
  113. fScheduler.AddTask('IdleCleaner',[],True,procedure(task : ITask)
  114. begin
  115. CheckForIdleItems;
  116. end)
  117. .StartInSeconds(10).RepeatEvery(fAutoFreeIdleItemTimeMs,TTimeMeasure.tmMilliseconds);
  118. fScheduler.Start;
  119. end;
  120. procedure TObjectPool<T>.CheckForIdleItems;
  121. var
  122. i : Integer;
  123. begin
  124. fLock.Enter;
  125. try
  126. for i := low(fPool) to High(fPool) do
  127. begin
  128. //check if item was not used for long time
  129. if (fPool[i] <> nil) and (fPool[i].RefCount = 1) and (MilliSecondsBetween(Now,fPool[i].LastAccess) > fAutoFreeIdleItemTimeMs) then
  130. begin
  131. fPool[i] := nil;
  132. end;
  133. end;
  134. finally
  135. fLock.Leave;
  136. end;
  137. end;
  138. function TObjectPool<T>.CreateDelegate(aCreateProc: TCreateDelegator<T>): IObjectPool<T>;
  139. begin
  140. fDelegate := aCreateProc;
  141. end;
  142. destructor TObjectPool<T>.Destroy;
  143. var
  144. i: Integer;
  145. begin
  146. fScheduler.Stop;
  147. fScheduler.Free;
  148. fLock.Enter;
  149. try
  150. for i := Low(fPool) to High(fPool) do fPool[i] := nil;
  151. SetLength(FPool,0);
  152. finally
  153. fLock.Leave;
  154. end;
  155. fLock.Free;
  156. fSemaphore.Free;
  157. inherited;
  158. end;
  159. function TObjectPool<T>.Get: IPoolItem<T>;
  160. var
  161. i : Integer;
  162. waitResult: TWaitResult;
  163. begin
  164. Result := nil;
  165. waitResult := fSemaphore.WaitFor(fWaitTimeoutMs);
  166. if waitResult <> TWaitResult.wrSignaled then raise Exception.Create('Connection Pool Timeout: Cannot obtain a connection');
  167. fLock.Enter;
  168. try
  169. if High(fPool) < fPoolSize then SetLength(fPool,High(fPool)+2);
  170. for i := Low(fPool) to High(fPool) do
  171. begin
  172. if fPool[i] = nil then
  173. begin
  174. fPool[i] := TPoolItem<T>.Create(fSemaphore,fLock,i,fDelegate);
  175. //writeln('create ' + i.ToString);
  176. Exit(fPool[i]);
  177. end;
  178. if fPool[i].RefCount = 1 then
  179. begin
  180. //writeln('get ' + i.ToString);
  181. Exit(fPool[i]);
  182. end;
  183. end;
  184. finally
  185. fLock.Leave;
  186. end;
  187. end;
  188. function TObjectPool<T>.GetPoolSize: Integer;
  189. begin
  190. Result := fPoolSize;
  191. end;
  192. function TObjectPool<T>.TimeoutMs(aTimeout: Integer): IObjectPool<T>;
  193. begin
  194. fWaitTimeoutMs := aTimeout;
  195. end;
  196. { TPoolItem<T> }
  197. function TPoolItem<T>.Item: T;
  198. begin
  199. fLastAccess := Now();
  200. Result := fItem;
  201. end;
  202. constructor TPoolItem<T>.Create(aSemaphore : TSemaphore; aLock : TCriticalSection; aItemIndex : Integer; aCreateProc : TCreateDelegator<T>);
  203. begin
  204. fLastAccess := Now();
  205. fItemIndex := aItemIndex;
  206. if Assigned(aCreateProc) then aCreateProc(fItem)
  207. else fItem := T.Create;
  208. fLock := aLock;
  209. fSemaphore := aSemaphore;
  210. end;
  211. destructor TPoolItem<T>.Destroy;
  212. begin
  213. if Assigned(fItem) then fItem.Free;
  214. inherited;
  215. end;
  216. function TPoolItem<T>.GetItemIndex: Integer;
  217. begin
  218. Result := fItemIndex;
  219. end;
  220. function TPoolItem<T>.GetLastAccess: TDateTime;
  221. begin
  222. Result := fLastAccess;
  223. end;
  224. function TPoolItem<T>.GetRefCount: Integer;
  225. begin
  226. Result := FRefCount;
  227. end;
  228. function TPoolItem<T>._AddRef: Integer;
  229. begin
  230. fLock.Enter;
  231. //writeln('enter');
  232. try
  233. Inc(FRefCount);
  234. Result := FRefCount;
  235. finally
  236. fLock.Leave;
  237. end;
  238. end;
  239. function TPoolItem<T>._Release: Integer;
  240. begin
  241. fLock.Enter;
  242. //writeln('exit');
  243. try
  244. Dec(fRefCount);
  245. Result := fRefCount;
  246. if Result = 0 then
  247. begin
  248. FreeAndNil(fItem);
  249. Destroy;
  250. end
  251. else fLastAccess := Now;
  252. finally
  253. fLock.Leave;
  254. if fRefCount = 1 then fSemaphore.Release;
  255. end;
  256. end;
  257. end.