Resource management system for automatic cleanup of objects and prevention of memory leaks.
Core functions for managing object destruction and cleanup lifecycle.
/**
* Destroy an object and run all registered destructors
* @param destroyable - Object to destroy
*/
function destroy(destroyable: object): void;
/**
* Check if an object is currently being destroyed
* @param destroyable - Object to check
* @returns Whether object is in destruction process
*/
function isDestroying(destroyable: object): boolean;
/**
* Check if an object has been completely destroyed
* @param destroyable - Object to check
* @returns Whether object is destroyed
*/
function isDestroyed(destroyable: object): boolean;Usage Examples:
import { destroy, isDestroying, isDestroyed } from "@ember/destroyable";
import Component from "@glimmer/component";
export default class WebSocketComponent extends Component {
socket = null;
constructor(owner, args) {
super(owner, args);
this.setupWebSocket();
}
setupWebSocket() {
this.socket = new WebSocket('ws://localhost:8080');
this.socket.onmessage = (event) => {
// Only process messages if component not destroyed
if (!isDestroyed(this)) {
this.handleMessage(JSON.parse(event.data));
}
};
}
handleMessage(data) {
// Check if destruction is in progress
if (isDestroying(this)) {
console.log('Component destroying, ignoring message');
return;
}
// Process message
this.args.onMessage?.(data);
}
willDestroy() {
super.willDestroy();
// Manual cleanup
if (this.socket) {
this.socket.close();
this.socket = null;
}
}
}
// Programmatic destruction
const component = SomeComponent.create();
// ... use component
destroy(component); // Triggers cleanup
console.log(isDestroyed(component)); // trueSystem for registering cleanup functions that run when objects are destroyed.
/**
* Register a destructor function to run when object is destroyed
* @param destroyable - Object to register destructor for
* @param destructor - Function to run on destruction
* @param eager - Whether to run destructor eagerly (before other cleanup)
*/
function registerDestructor(destroyable: object, destructor: (destroyable: object) => void, eager?: boolean): void;
/**
* Unregister a previously registered destructor
* @param destroyable - Object the destructor was registered for
* @param destructor - Destructor function to unregister
*/
function unregisterDestructor(destroyable: object, destructor: (destroyable: object) => void): void;Usage Examples:
import { registerDestructor, unregisterDestructor } from "@ember/destroyable";
import Service from "@ember/service";
export default class DatabaseService extends Service {
connections = new Map();
init() {
super.init();
// Register cleanup for all connections
registerDestructor(this, (service) => {
console.log('Cleaning up database connections...');
service.connections.forEach(connection => {
connection.close();
});
service.connections.clear();
});
}
createConnection(name, config) {
const connection = new DatabaseConnection(config);
this.connections.set(name, connection);
// Register destructor for individual connection
const connectionDestructor = () => {
console.log(`Closing connection: ${name}`);
connection.close();
this.connections.delete(name);
};
registerDestructor(this, connectionDestructor);
// Return function to manually close connection
return () => {
unregisterDestructor(this, connectionDestructor);
connectionDestructor();
};
}
}
// Component with resource cleanup
export default class TimerComponent extends Component {
timerId = null;
constructor(owner, args) {
super(owner, args);
this.startTimer();
// Register cleanup for timer
registerDestructor(this, (component) => {
if (component.timerId) {
clearInterval(component.timerId);
component.timerId = null;
}
});
}
startTimer() {
this.timerId = setInterval(() => {
if (!isDestroyed(this)) {
this.args.onTick?.();
}
}, 1000);
}
}System for associating child objects with parents for automatic cleanup.
/**
* Associate a child destroyable with a parent for automatic cleanup
* When parent is destroyed, child will be destroyed too
* @param parent - Parent object
* @param child - Child object to associate
*/
function associateDestroyableChild(parent: object, child: object): void;Usage Examples:
import { associateDestroyableChild } from "@ember/destroyable";
import EmberObject from "@ember/object";
export default class ParentService extends Service {
children = new Set();
createChild(config) {
const child = ChildObject.create(config);
this.children.add(child);
// Associate child with parent for automatic cleanup
associateDestroyableChild(this, child);
return child;
}
createWorker(taskConfig) {
const worker = new Worker('worker.js');
const workerWrapper = EmberObject.create({
worker,
postMessage(data) {
this.worker.postMessage(data);
},
terminate() {
this.worker.terminate();
}
});
// Auto-cleanup worker when service is destroyed
associateDestroyableChild(this, workerWrapper);
// Register destructor for worker cleanup
registerDestructor(workerWrapper, (wrapper) => {
wrapper.worker.terminate();
});
return workerWrapper;
}
}
// Factory pattern with automatic cleanup
class ResourceFactory {
static createResource(parent, type, config) {
let resource;
switch (type) {
case 'database':
resource = DatabaseResource.create(config);
break;
case 'cache':
resource = CacheResource.create(config);
break;
case 'queue':
resource = QueueResource.create(config);
break;
default:
throw new Error(`Unknown resource type: ${type}`);
}
// Associate with parent for cleanup
associateDestroyableChild(parent, resource);
return resource;
}
}
export default class ApplicationService extends Service {
init() {
super.init();
// Create resources that will be auto-cleaned up
this.database = ResourceFactory.createResource(this, 'database', {
host: 'localhost',
port: 5432
});
this.cache = ResourceFactory.createResource(this, 'cache', {
maxSize: 1000
});
this.queue = ResourceFactory.createResource(this, 'queue', {
maxJobs: 10
});
}
}Advanced patterns for complex cleanup scenarios.
/**
* Enable eager destruction (immediate cleanup) for an object
* @param destroyable - Object to enable eager destruction for
*/
function enableDestroyableTracking(destroyable: object): void;
/**
* Check if destruction tracking is enabled for an object
* @param destroyable - Object to check
* @returns Whether tracking is enabled
*/
function isDestroyableTrackingEnabled(destroyable: object): boolean;Usage Examples:
import {
registerDestructor,
associateDestroyableChild,
enableDestroyableTracking
} from "@ember/destroyable";
// Resource pool with automatic cleanup
export default class ResourcePool extends Service {
pool = [];
activeResources = new Set();
init() {
super.init();
// Enable tracking for immediate cleanup feedback
enableDestroyableTracking(this);
// Register pool cleanup
registerDestructor(this, (pool) => {
console.log(`Cleaning up resource pool with ${pool.activeResources.size} active resources`);
// Force cleanup of all active resources
pool.activeResources.forEach(resource => {
resource.forceCleanup();
});
pool.pool.length = 0;
pool.activeResources.clear();
}, true); // Eager cleanup
}
acquireResource() {
let resource = this.pool.pop();
if (!resource) {
resource = this.createResource();
}
this.activeResources.add(resource);
// Auto-cleanup when parent is destroyed
associateDestroyableChild(this, resource);
return resource;
}
releaseResource(resource) {
this.activeResources.delete(resource);
if (resource.isReusable() && !isDestroyed(this)) {
resource.reset();
this.pool.push(resource);
} else {
destroy(resource);
}
}
createResource() {
const resource = ResourceObject.create({
pool: this,
forceCleanup() {
this.cleanup();
this.pool = null;
}
});
// Register individual resource cleanup
registerDestructor(resource, (res) => {
if (res.pool) {
res.pool.activeResources.delete(res);
}
res.cleanup();
});
return resource;
}
}Utilities and patterns for preventing common memory leaks.
/**
* Create a weak reference that doesn't prevent garbage collection
* @param target - Object to create weak reference to
* @returns Weak reference wrapper
*/
function createWeakRef(target: object): WeakRef<object>;
/**
* Register callback to run when object is garbage collected
* @param target - Object to watch
* @param callback - Function to run on GC
* @returns FinalizationRegistry entry
*/
function onGarbageCollect(target: object, callback: () => void): any;Usage Examples:
// Preventing circular references with weak references
export default class EventBus extends Service {
listeners = new Map();
weakRefs = new Map();
subscribe(target, eventName, callback) {
// Create weak reference to prevent memory leaks
const weakRef = new WeakRef(target);
const listenerId = Math.random().toString(36);
const listener = {
id: listenerId,
eventName,
callback,
target: weakRef
};
if (!this.listeners.has(eventName)) {
this.listeners.set(eventName, new Set());
}
this.listeners.get(eventName).add(listener);
this.weakRefs.set(listenerId, listener);
// Cleanup when target is destroyed
registerDestructor(target, () => {
this.unsubscribe(listenerId);
});
return listenerId;
}
emit(eventName, data) {
const listeners = this.listeners.get(eventName);
if (!listeners) return;
// Clean up dead weak references
const deadListeners = [];
listeners.forEach(listener => {
const target = listener.target.deref();
if (target && !isDestroyed(target)) {
listener.callback.call(target, data);
} else {
deadListeners.push(listener);
}
});
// Remove dead listeners
deadListeners.forEach(listener => {
listeners.delete(listener);
this.weakRefs.delete(listener.id);
});
}
unsubscribe(listenerId) {
const listener = this.weakRefs.get(listenerId);
if (listener) {
const listeners = this.listeners.get(listener.eventName);
if (listeners) {
listeners.delete(listener);
}
this.weakRefs.delete(listenerId);
}
}
}interface Destroyable {
/** Whether object is being destroyed */
isDestroying?: boolean;
/** Whether object is destroyed */
isDestroyed?: boolean;
/** Cleanup method (optional) */
destroy?(): void;
/** Pre-destruction hook (optional) */
willDestroy?(): void;
}
interface DestructorFunction {
/** Function called when object is destroyed */
(destroyable: object): void;
}
interface DestroyableChild extends Destroyable {
/** Parent destroyable object */
parent?: Destroyable;
}
interface ResourceWrapper {
/** Wrapped resource */
resource: any;
/** Cleanup function */
cleanup(): void;
/** Whether resource is reusable */
isReusable(): boolean;
/** Reset resource for reuse */
reset(): void;
}