Pool.js 11 KB

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