Pool.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Pool = void 0;
  4. const Deferred_1 = require("./Deferred");
  5. const AggregateError_1 = require("./AggregateError");
  6. class Pool {
  7. constructor(factory) {
  8. this.log = false;
  9. if (!factory.create) {
  10. throw new Error('create function is required');
  11. }
  12. if (!factory.destroy) {
  13. throw new Error('destroy function is required');
  14. }
  15. if (!factory.validate) {
  16. throw new Error('validate function is required');
  17. }
  18. if (typeof factory.min !== 'number' ||
  19. factory.min < 0 ||
  20. factory.min !== Math.round(factory.min)) {
  21. throw new Error('min must be an integer >= 0');
  22. }
  23. if (typeof factory.max !== 'number' ||
  24. factory.max <= 0 ||
  25. factory.max !== Math.round(factory.max)) {
  26. throw new Error('max must be an integer > 0');
  27. }
  28. if (factory.min > factory.max) {
  29. throw new Error('max is smaller than min');
  30. }
  31. if (factory.maxUses !== undefined &&
  32. (typeof factory.maxUses !== 'number' || factory.maxUses < 0)) {
  33. throw new Error('maxUses must be an integer >= 0');
  34. }
  35. this.idleTimeoutMillis = factory.idleTimeoutMillis || 30000;
  36. this.acquireTimeoutMillis = factory.acquireTimeoutMillis || 30000;
  37. this.reapIntervalMillis = factory.reapIntervalMillis || 1000;
  38. this.maxUsesPerResource = factory.maxUses || Infinity;
  39. this.log = factory.log || false;
  40. this._factory = factory;
  41. this._count = 0;
  42. this._draining = false;
  43. this._pendingAcquires = [];
  44. this._inUseObjects = [];
  45. this._availableObjects = [];
  46. this._removeIdleScheduled = false;
  47. }
  48. get size() {
  49. return this._count;
  50. }
  51. get name() {
  52. return this._factory.name;
  53. }
  54. get available() {
  55. return this._availableObjects.length;
  56. }
  57. get using() {
  58. return this._inUseObjects.length;
  59. }
  60. get waiting() {
  61. return this._pendingAcquires.length;
  62. }
  63. get maxSize() {
  64. return this._factory.max;
  65. }
  66. get minSize() {
  67. return this._factory.min;
  68. }
  69. _log(message, level) {
  70. if (typeof this.log === 'function') {
  71. this.log(message, level);
  72. }
  73. else if (this.log) {
  74. console.log(`${level.toUpperCase()} pool ${this.name || ''} - ${message}`);
  75. }
  76. }
  77. _removeIdle() {
  78. const toRemove = [];
  79. const now = Date.now();
  80. let i;
  81. let available = this._availableObjects.length;
  82. const maxRemovable = this.size - this.minSize;
  83. let timeout;
  84. this._removeIdleScheduled = false;
  85. for (i = 0; i < available && maxRemovable > toRemove.length; i++) {
  86. timeout = this._availableObjects[i].timeout;
  87. if (now >= timeout) {
  88. this._log('removeIdle() destroying obj - now:' + now + ' timeout:' + timeout, 'verbose');
  89. toRemove.push(this._availableObjects[i].resource);
  90. }
  91. }
  92. toRemove.forEach(this.destroy, this);
  93. available = this._availableObjects.length;
  94. if (available > 0) {
  95. this._log('this._availableObjects.length=' + available, 'verbose');
  96. this._scheduleRemoveIdle();
  97. }
  98. else {
  99. this._log('removeIdle() all objects removed', 'verbose');
  100. }
  101. }
  102. _scheduleRemoveIdle() {
  103. if (!this._removeIdleScheduled) {
  104. this._removeIdleScheduled = true;
  105. this._removeIdleTimer = setTimeout(() => {
  106. this._removeIdle();
  107. }, this.reapIntervalMillis);
  108. }
  109. }
  110. _dispense() {
  111. let wrappedResource = null;
  112. const waitingCount = this._pendingAcquires.length;
  113. this._log(`dispense() clients=${waitingCount} available=${this._availableObjects.length}`, 'info');
  114. if (waitingCount < 1) {
  115. return;
  116. }
  117. while (this._availableObjects.length > 0) {
  118. this._log('dispense() - reusing obj', 'verbose');
  119. wrappedResource = this._availableObjects[this._availableObjects.length - 1];
  120. if (!this._factory.validate(wrappedResource.resource)) {
  121. this.destroy(wrappedResource.resource);
  122. continue;
  123. }
  124. this._availableObjects.pop();
  125. this._addResourceToInUseObjects(wrappedResource.resource, wrappedResource.useCount);
  126. const deferred = this._pendingAcquires.shift();
  127. return deferred.resolve(wrappedResource.resource);
  128. }
  129. if (this.size < this.maxSize) {
  130. this._createResource();
  131. }
  132. }
  133. _createResource() {
  134. this._count += 1;
  135. this._log(`createResource() - creating obj - count=${this.size} min=${this.minSize} max=${this.maxSize}`, 'verbose');
  136. this._factory
  137. .create()
  138. .then((resource) => {
  139. const deferred = this._pendingAcquires.shift();
  140. if (deferred) {
  141. this._addResourceToInUseObjects(resource, 0);
  142. deferred.resolve(resource);
  143. }
  144. else {
  145. this._addResourceToAvailableObjects(resource, 0);
  146. }
  147. })
  148. .catch((error) => {
  149. const deferred = this._pendingAcquires.shift();
  150. this._count -= 1;
  151. if (this._count < 0)
  152. this._count = 0;
  153. if (deferred) {
  154. deferred.reject(error);
  155. }
  156. process.nextTick(() => {
  157. this._dispense();
  158. });
  159. });
  160. }
  161. _addResourceToAvailableObjects(resource, useCount) {
  162. const wrappedResource = {
  163. resource: resource,
  164. useCount: useCount,
  165. timeout: Date.now() + this.idleTimeoutMillis,
  166. };
  167. this._availableObjects.push(wrappedResource);
  168. this._dispense();
  169. this._scheduleRemoveIdle();
  170. }
  171. _addResourceToInUseObjects(resource, useCount) {
  172. const wrappedResource = {
  173. resource: resource,
  174. useCount: useCount,
  175. };
  176. this._inUseObjects.push(wrappedResource);
  177. }
  178. _ensureMinimum() {
  179. let i, diff;
  180. if (!this._draining && this.size < this.minSize) {
  181. diff = this.minSize - this.size;
  182. for (i = 0; i < diff; i++) {
  183. this._createResource();
  184. }
  185. }
  186. }
  187. acquire() {
  188. if (this._draining) {
  189. return Promise.reject(new Error('pool is draining and cannot accept work'));
  190. }
  191. const deferred = new Deferred_1.Deferred();
  192. deferred.registerTimeout(this.acquireTimeoutMillis, () => {
  193. this._pendingAcquires = this._pendingAcquires.filter((pending) => pending !== deferred);
  194. });
  195. this._pendingAcquires.push(deferred);
  196. this._dispense();
  197. return deferred.promise();
  198. }
  199. release(resource) {
  200. if (this._availableObjects.some((resourceWithTimeout) => resourceWithTimeout.resource === resource)) {
  201. this._log('release called twice for the same resource: ' + new Error().stack, 'error');
  202. return;
  203. }
  204. const index = this._inUseObjects.findIndex((wrappedResource) => wrappedResource.resource === resource);
  205. if (index < 0) {
  206. this._log('attempt to release an invalid resource: ' + new Error().stack, 'error');
  207. return;
  208. }
  209. const wrappedResource = this._inUseObjects[index];
  210. wrappedResource.useCount += 1;
  211. if (wrappedResource.useCount >= this.maxUsesPerResource) {
  212. this._log('release() destroying obj - useCount:' +
  213. wrappedResource.useCount +
  214. ' maxUsesPerResource:' +
  215. this.maxUsesPerResource, 'verbose');
  216. this.destroy(wrappedResource.resource);
  217. this._dispense();
  218. }
  219. else {
  220. this._inUseObjects.splice(index, 1);
  221. this._addResourceToAvailableObjects(wrappedResource.resource, wrappedResource.useCount);
  222. }
  223. }
  224. async destroy(resource) {
  225. const available = this._availableObjects.length;
  226. const using = this._inUseObjects.length;
  227. this._availableObjects = this._availableObjects.filter((object) => object.resource !== resource);
  228. this._inUseObjects = this._inUseObjects.filter((object) => object.resource !== resource);
  229. if (available === this._availableObjects.length &&
  230. using === this._inUseObjects.length) {
  231. this._ensureMinimum();
  232. return;
  233. }
  234. this._count -= 1;
  235. if (this._count < 0)
  236. this._count = 0;
  237. try {
  238. await this._factory.destroy(resource);
  239. }
  240. finally {
  241. this._ensureMinimum();
  242. if (!this._draining) {
  243. process.nextTick(() => {
  244. this._dispense();
  245. });
  246. }
  247. }
  248. }
  249. drain() {
  250. this._log('draining', 'info');
  251. this._draining = true;
  252. const check = (callback) => {
  253. if (this._pendingAcquires.length > 0) {
  254. this._dispense();
  255. setTimeout(() => {
  256. check(callback);
  257. }, 100);
  258. return;
  259. }
  260. if (this._availableObjects.length !== this._count) {
  261. setTimeout(() => {
  262. check(callback);
  263. }, 100);
  264. return;
  265. }
  266. callback();
  267. };
  268. return new Promise((resolve) => check(resolve));
  269. }
  270. async destroyAllNow() {
  271. this._log('force destroying all objects', 'info');
  272. this._removeIdleScheduled = false;
  273. clearTimeout(this._removeIdleTimer);
  274. const resources = this._availableObjects.map((resource) => resource.resource);
  275. const errors = [];
  276. for (const resource of resources) {
  277. try {
  278. await this.destroy(resource);
  279. }
  280. catch (ex) {
  281. this._log('Error destroying resource: ' + ex.stack, 'error');
  282. errors.push(ex);
  283. }
  284. }
  285. if (errors.length > 0) {
  286. throw new AggregateError_1.AggregateError(errors);
  287. }
  288. }
  289. }
  290. exports.Pool = Pool;
  291. //# sourceMappingURL=Pool.js.map