Quick.Pooling.pas 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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. Result := Self;
  99. fAutoFreeIdleItemTimeMs := aIdleTimeMs;
  100. end;
  101. constructor TObjectPool<T>.Create(aPoolSize : Integer; aAutoFreeIdleItemTimeMs : Integer = 30000; aCreateProc : TCreateDelegator<T> = nil);
  102. begin
  103. fLock := TCriticalSection.Create;
  104. fPoolSize := aPoolSize;
  105. fWaitTimeoutMs := 30000;
  106. fDelegate := aCreateProc;
  107. fAutoFreeIdleItemTimeMs := aAutoFreeIdleItemTimeMs;
  108. fSemaphore := TSemaphore.Create(nil,fPoolSize,fPoolSize,'');
  109. CreateScheduler;
  110. end;
  111. procedure TObjectPool<T>.CreateScheduler;
  112. begin
  113. fScheduler := TScheduledTasks.Create;
  114. fScheduler.AddTask('IdleCleaner',[],True,procedure(task : ITask)
  115. begin
  116. CheckForIdleItems;
  117. end)
  118. .StartInSeconds(10).RepeatEvery(fAutoFreeIdleItemTimeMs,TTimeMeasure.tmMilliseconds);
  119. fScheduler.Start;
  120. end;
  121. procedure TObjectPool<T>.CheckForIdleItems;
  122. var
  123. i : Integer;
  124. begin
  125. fLock.Enter;
  126. try
  127. for i := low(fPool) to High(fPool) do
  128. begin
  129. //check if item was not used for long time
  130. if (fPool[i] <> nil) and (fPool[i].RefCount = 1) and (MilliSecondsBetween(Now,fPool[i].LastAccess) > fAutoFreeIdleItemTimeMs) then
  131. begin
  132. fPool[i] := nil;
  133. end;
  134. end;
  135. finally
  136. fLock.Leave;
  137. end;
  138. end;
  139. function TObjectPool<T>.CreateDelegate(aCreateProc: TCreateDelegator<T>): IObjectPool<T>;
  140. begin
  141. Result := Self;
  142. fDelegate := aCreateProc;
  143. end;
  144. destructor TObjectPool<T>.Destroy;
  145. var
  146. i: Integer;
  147. begin
  148. fScheduler.Stop;
  149. fScheduler.Free;
  150. fLock.Enter;
  151. try
  152. for i := Low(fPool) to High(fPool) do fPool[i] := nil;
  153. SetLength(FPool,0);
  154. finally
  155. fLock.Leave;
  156. end;
  157. fLock.Free;
  158. fSemaphore.Free;
  159. inherited;
  160. end;
  161. function TObjectPool<T>.Get: IPoolItem<T>;
  162. var
  163. i : Integer;
  164. waitResult: TWaitResult;
  165. begin
  166. Result := nil;
  167. waitResult := fSemaphore.WaitFor(fWaitTimeoutMs);
  168. if waitResult <> TWaitResult.wrSignaled then raise Exception.Create('Connection Pool Timeout: Cannot obtain a connection');
  169. fLock.Enter;
  170. try
  171. if High(fPool) < fPoolSize then SetLength(fPool,High(fPool)+2);
  172. for i := Low(fPool) to High(fPool) do
  173. begin
  174. if fPool[i] = nil then
  175. begin
  176. fPool[i] := TPoolItem<T>.Create(fSemaphore,fLock,i,fDelegate);
  177. //writeln('create ' + i.ToString);
  178. Exit(fPool[i]);
  179. end;
  180. if fPool[i].RefCount = 1 then
  181. begin
  182. //writeln('get ' + i.ToString);
  183. Exit(fPool[i]);
  184. end;
  185. end;
  186. finally
  187. fLock.Leave;
  188. end;
  189. end;
  190. function TObjectPool<T>.GetPoolSize: Integer;
  191. begin
  192. Result := fPoolSize;
  193. end;
  194. function TObjectPool<T>.TimeoutMs(aTimeout: Integer): IObjectPool<T>;
  195. begin
  196. fWaitTimeoutMs := aTimeout;
  197. end;
  198. { TPoolItem<T> }
  199. function TPoolItem<T>.Item: T;
  200. begin
  201. fLastAccess := Now();
  202. Result := fItem;
  203. end;
  204. constructor TPoolItem<T>.Create(aSemaphore : TSemaphore; aLock : TCriticalSection; aItemIndex : Integer; aCreateProc : TCreateDelegator<T>);
  205. begin
  206. fLastAccess := Now();
  207. fItemIndex := aItemIndex;
  208. if Assigned(aCreateProc) then aCreateProc(fItem)
  209. else fItem := T.Create;
  210. fLock := aLock;
  211. fSemaphore := aSemaphore;
  212. end;
  213. destructor TPoolItem<T>.Destroy;
  214. begin
  215. if Assigned(fItem) then fItem.Free;
  216. inherited;
  217. end;
  218. function TPoolItem<T>.GetItemIndex: Integer;
  219. begin
  220. Result := fItemIndex;
  221. end;
  222. function TPoolItem<T>.GetLastAccess: TDateTime;
  223. begin
  224. Result := fLastAccess;
  225. end;
  226. function TPoolItem<T>.GetRefCount: Integer;
  227. begin
  228. Result := FRefCount;
  229. end;
  230. function TPoolItem<T>._AddRef: Integer;
  231. begin
  232. fLock.Enter;
  233. //writeln('enter');
  234. try
  235. Inc(FRefCount);
  236. Result := FRefCount;
  237. finally
  238. fLock.Leave;
  239. end;
  240. end;
  241. function TPoolItem<T>._Release: Integer;
  242. begin
  243. fLock.Enter;
  244. //writeln('exit');
  245. try
  246. Dec(fRefCount);
  247. Result := fRefCount;
  248. if Result = 0 then
  249. begin
  250. FreeAndNil(fItem);
  251. Destroy;
  252. end
  253. else fLastAccess := Now;
  254. finally
  255. fLock.Leave;
  256. if fRefCount = 1 then fSemaphore.Release;
  257. end;
  258. end;
  259. end.