lifetime.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Scope = exports.WeakLifetime = exports.StaticLifetime = exports.Lifetime = void 0;
  4. const asyncify_helpers_1 = require("./asyncify-helpers");
  5. const debug_1 = require("./debug");
  6. const errors_1 = require("./errors");
  7. /**
  8. * A lifetime prevents access to a value after the lifetime has been
  9. * [[dispose]]ed.
  10. *
  11. * Typically, quickjs-emscripten uses Lifetimes to protect C memory pointers.
  12. */
  13. class Lifetime {
  14. /**
  15. * When the Lifetime is disposed, it will call `disposer(_value)`. Use the
  16. * disposer function to implement whatever cleanup needs to happen at the end
  17. * of `value`'s lifetime.
  18. *
  19. * `_owner` is not used or controlled by the lifetime. It's just metadata for
  20. * the creator.
  21. */
  22. constructor(_value, copier, disposer, _owner) {
  23. this._value = _value;
  24. this.copier = copier;
  25. this.disposer = disposer;
  26. this._owner = _owner;
  27. this._alive = true;
  28. this._constructorStack = debug_1.QTS_DEBUG ? new Error("Lifetime constructed").stack : undefined;
  29. }
  30. get alive() {
  31. return this._alive;
  32. }
  33. /**
  34. * The value this Lifetime protects. You must never retain the value - it
  35. * may become invalid, leading to memory errors.
  36. *
  37. * @throws If the lifetime has been [[dispose]]d already.
  38. */
  39. get value() {
  40. this.assertAlive();
  41. return this._value;
  42. }
  43. get owner() {
  44. return this._owner;
  45. }
  46. get dupable() {
  47. return !!this.copier;
  48. }
  49. /**
  50. * Create a new handle pointing to the same [[value]].
  51. */
  52. dup() {
  53. this.assertAlive();
  54. if (!this.copier) {
  55. throw new Error("Non-dupable lifetime");
  56. }
  57. return new Lifetime(this.copier(this._value), this.copier, this.disposer, this._owner);
  58. }
  59. consume(map) {
  60. this.assertAlive();
  61. const result = map(this);
  62. this.dispose();
  63. return result;
  64. }
  65. /**
  66. * Dispose of [[value]] and perform cleanup.
  67. */
  68. dispose() {
  69. this.assertAlive();
  70. if (this.disposer) {
  71. this.disposer(this._value);
  72. }
  73. this._alive = false;
  74. }
  75. assertAlive() {
  76. if (!this.alive) {
  77. if (this._constructorStack) {
  78. throw new errors_1.QuickJSUseAfterFree(`Lifetime not alive\n${this._constructorStack}\nLifetime used`);
  79. }
  80. throw new errors_1.QuickJSUseAfterFree("Lifetime not alive");
  81. }
  82. }
  83. }
  84. exports.Lifetime = Lifetime;
  85. /**
  86. * A Lifetime that lives forever. Used for constants.
  87. */
  88. class StaticLifetime extends Lifetime {
  89. constructor(value, owner) {
  90. super(value, undefined, undefined, owner);
  91. }
  92. // Static lifetime doesn't need a copier to be copiable
  93. get dupable() {
  94. return true;
  95. }
  96. // Copy returns the same instance.
  97. dup() {
  98. return this;
  99. }
  100. // Dispose does nothing.
  101. dispose() { }
  102. }
  103. exports.StaticLifetime = StaticLifetime;
  104. /**
  105. * A Lifetime that does not own its `value`. A WeakLifetime never calls its
  106. * `disposer` function, but can be `dup`ed to produce regular lifetimes that
  107. * do.
  108. *
  109. * Used for function arguments.
  110. */
  111. class WeakLifetime extends Lifetime {
  112. constructor(value, copier, disposer, owner) {
  113. // We don't care if the disposer doesn't support freeing T
  114. super(value, copier, disposer, owner);
  115. }
  116. dispose() {
  117. this._alive = false;
  118. }
  119. }
  120. exports.WeakLifetime = WeakLifetime;
  121. function scopeFinally(scope, blockError) {
  122. // console.log('scopeFinally', scope, blockError)
  123. let disposeError;
  124. try {
  125. scope.dispose();
  126. }
  127. catch (error) {
  128. disposeError = error;
  129. }
  130. if (blockError && disposeError) {
  131. Object.assign(blockError, {
  132. message: `${blockError.message}\n Then, failed to dispose scope: ${disposeError.message}`,
  133. disposeError,
  134. });
  135. throw blockError;
  136. }
  137. if (blockError || disposeError) {
  138. throw blockError || disposeError;
  139. }
  140. }
  141. /**
  142. * Scope helps reduce the burden of manually tracking and disposing of
  143. * Lifetimes. See [[withScope]]. and [[withScopeAsync]].
  144. */
  145. class Scope {
  146. constructor() {
  147. this._disposables = new Lifetime(new Set());
  148. }
  149. /**
  150. * Run `block` with a new Scope instance that will be disposed after the block returns.
  151. * Inside `block`, call `scope.manage` on each lifetime you create to have the lifetime
  152. * automatically disposed after the block returns.
  153. *
  154. * @warning Do not use with async functions. Instead, use [[withScopeAsync]].
  155. */
  156. static withScope(block) {
  157. const scope = new Scope();
  158. let blockError;
  159. try {
  160. return block(scope);
  161. }
  162. catch (error) {
  163. blockError = error;
  164. throw error;
  165. }
  166. finally {
  167. scopeFinally(scope, blockError);
  168. }
  169. }
  170. static withScopeMaybeAsync(_this, block) {
  171. return (0, asyncify_helpers_1.maybeAsync)(undefined, function* (awaited) {
  172. const scope = new Scope();
  173. let blockError;
  174. try {
  175. return yield* awaited.of(block.call(_this, awaited, scope));
  176. }
  177. catch (error) {
  178. blockError = error;
  179. throw error;
  180. }
  181. finally {
  182. scopeFinally(scope, blockError);
  183. }
  184. });
  185. }
  186. /**
  187. * Run `block` with a new Scope instance that will be disposed after the
  188. * block's returned promise settles. Inside `block`, call `scope.manage` on each
  189. * lifetime you create to have the lifetime automatically disposed after the
  190. * block returns.
  191. */
  192. static async withScopeAsync(block) {
  193. const scope = new Scope();
  194. let blockError;
  195. try {
  196. return await block(scope);
  197. }
  198. catch (error) {
  199. blockError = error;
  200. throw error;
  201. }
  202. finally {
  203. scopeFinally(scope, blockError);
  204. }
  205. }
  206. /**
  207. * Track `lifetime` so that it is disposed when this scope is disposed.
  208. */
  209. manage(lifetime) {
  210. this._disposables.value.add(lifetime);
  211. return lifetime;
  212. }
  213. get alive() {
  214. return this._disposables.alive;
  215. }
  216. dispose() {
  217. const lifetimes = Array.from(this._disposables.value.values()).reverse();
  218. for (const lifetime of lifetimes) {
  219. if (lifetime.alive) {
  220. lifetime.dispose();
  221. }
  222. }
  223. this._disposables.dispose();
  224. }
  225. }
  226. exports.Scope = Scope;
  227. //# sourceMappingURL=lifetime.js.map