Prevent AudioWorkletNode memory leak - javascript

I have an AudioWorkletNode that is a member of a class instance. When I delete/kill/remove that instance, the AudioWorkletNode and MessagePort leak.
Before deleting the instance, I make sure the corresponding AudioWorkletProcessor's process method isn't running. I've also tried calling the port's close() method, and even setting the AudioWorkletNode to null. It also doesn't seem to matter if the node is connected or disconnected at the time. It leaks either way.
To establish the audioWorklet module:
AG_Recorder = class AG_Recorder {
constructor(outNode) {
AG_AudioManager.audioCtx.audioWorklet.addModule( '/audioWorkers/recorder.js').then(() => {
this.recorderNode = new AudioWorkletNode(AG_AudioManager.audioCtx, 'recorder');
this.recorderNode.connect(outNode);
this.recorderNode.port.onmessage = (event) => {
this.handleMessage(event.data);
};
}).catch(function (err) {
console.log('recorder audioworklet error: ', err.name, err.message);
});
}
}
And the processor, strongly abbreviated for relevancy:
class RecorderWorkletNode extends AudioWorkletProcessor {
constructor (options) {
super(options);
this._running = true;
this.port.onmessage = event => {
if (event.data.release) {
this._running = false;
}
}
this.port.start();
}
process (inputs, outputs, parameters) {
return this._running;
}
}
And before the node is disconnected, and the AG_Recorder instance is deleted, I've tried doing this:
release() {
this.recorderNode.port.postMessage({release: true});
this.recorderNode.port.onmessage = null;
this.recorderNode.port.close();
this.recorderNode = null;
}

It seems to be a confirmed bug for Chromium:
https://bugs.chromium.org/p/chromium/issues/detail?id=1298955
Update: not very sure about Firefox etc

Related

Dependency injection and method restriction

I have a class that manages a WebSocket connection and provides the methods to subscribe to this WebSocket. An instance of this class may be passed to many child objects that subscribe to this WebSocket via these methods:
const client = new WebSocketManager();
await client.connect();
const objA = new ClassA(client);
const objB = new ClassB(client);
client.close(); // at a future point in time
The problem is that client exposes critical methods like connect and close. If objA calls close, objB would fail for no apparent reason. Those two methods should be private to the children, but public to the initiator of the parent object. Is there a pattern to solve this problem? Two methods are presented below. Are any of those two patterns acceptable or are there better solutions? I would like to hear your opinion.
Method 1: Define an owner of the parent object. Restricted methods require a reference to the owner.
class Parent {
#owner;
constructor(owner) {
this.#owner = owner;
}
restricedMethod(owner) {
if(owner !== this.#owner)
throw Error('not authorized');
console.log('restricedMethod called');
}
accessibleMethod() {
console.log('accessibleMethod called');
}
}
class Child {
constructor(dependency) {
this.dependency = dependency;
}
callAccessibleMethod() {
this.dependency.accessibleMethod();
}
callRestricedMethod() {
this.dependency.restricedMethod();
}
}
const parent = new Parent(this);
// 'this' is the owner of 'parent'
const child = new Child(parent);
child.callAccessibleMethod();
// accessibleMethod called
try { child.callRestricedMethod(); } catch(e) { console.log(e.message); }
// Error: not autherized
parent.restricedMethod(this); // only the owner can call this method
// restricedMethod called
Method 2: Create a new parent object on the fly that only contains the methods that the child may access.
class Parent {
restricedMethod(){
console.log('restricedMethod called');
}
accessibleMethod(){
console.log('accessibleMethod called');
}
}
class Child {
constructor(dependency) {
this.dependency = dependency;
}
callAccessibleMethod() {
this.dependency.accessibleMethod();
}
callRestricedMethod() {
this.dependency.restricedMethod();
}
}
class MethodSelection {
constructor(object, methods) {
for (const method of methods)
this[method] = object[method];
}
}
const parent = new Parent();
// select methods from parent object and create new object
const restricted = new MethodSelection(parent, ['accessibleMethod']);
console.log(restricted.accessibleMethod === parent.accessibleMethod);
// true
const child = new Child(restricted);
child.callAccessibleMethod();
// accessibleMethod called
try { child.callRestricedMethod(); } catch(e) { console.log(e.message); }
// TypeError: this.dependency.restricedMethod is not a function
parent.restricedMethod();
// restricedMethod called

Implementing kind of a delegation pattern in Javascript

I have two classes, A and B. What I am trying to do is to pass data from A to B after receiving a message from sockets.
This is simplified look of how classes are defined:
class A:
export default class A {
client;
callbacks;
constructor() {
this.callbacks = {
open: () => this.client.logger.debug('open'),
close: () => this.client.logger.debug('closed'),
message: (data) => {this.client.logger.log(data)}, //I want to pass this data object to class B
};
this.client = new Spot(constants.apiKey, constants.apiSecret, {
baseURL: constants.baseURL,
wsURL: constants.wsURL,
});
this.client.userData(listenKey, this.callbacks);
}
}
I already have a property of A in class definition of B:
export default class B {
account;
constructor() {
this.account = new A();
}
}
What would be a correct/standard way to connect these two so I get a 'data' object from class A every time the socket message callback from class A is triggered?
I am a bit new with JS, but on iOS we would use a delegation pattern, with a protocol, that says:
class A will have a delegate property.
A delegate (class B) must implement a protocol (in this case it would be a requirement to implement method called didReceiveMessage(data).
After that, when a message is received in class A, we would just do(in socket message callback shown above) something like this.delegate.didReceiveMessage(data).
Protocol usage here is not important generally, but it is a plus, cause from A class, we can only access didReceiveData(data) method trough a delegate property, and nothing else (other properties / methods of class B are not visible). At least that is how it works in Swift/Obj-C. I just mentioned it, cause I am curious is this how it is done in JS too.
I guess there is some similar mechanism in Javascript, or some more standard/better way to achieve this kind of data sending between objects?
on iOS we would use a delegation pattern, with a protocol
You can do it exactly as you described:
export default class A {
client;
delegate;
constructor(delegate) {
this.delegate = delegate;
this.client = new Spot(constants.apiKey, constants.apiSecret, {
baseURL: constants.baseURL,
wsURL: constants.wsURL,
});
const callbacks = {
open: () => this.client.logger.debug('open'),
close: () => this.client.logger.debug('closed'),
message: (data) => this.delegate.didReceiveMessage(data),
};
this.client.userData(listenKey, callbacks);
}
}
export default class B {
account;
constructor() {
this.account = new A(this);
}
didReceiveMessage(data) {
console.log(data); // or whatever
}
}
There is no interface (protocol) declaration that would tell A which properties and methods it may access on the passed delegate, but the contract exists of course. You should document it in prose. (Or use TypeScript).
Notice also how your class A interacts with the Spot client, it uses very much the same pattern of passing an object with event handler methods.
A simpler pattern in JavaScript, if you just need a single method in your protocol, is to pass a callable function only:
export default class A {
client;
constructor(onMessage) {
this.client = new Spot(constants.apiKey, constants.apiSecret, {
baseURL: constants.baseURL,
wsURL: constants.wsURL,
});
this.client.userData(listenKey, {
open: () => this.client.logger.debug('open'),
close: () => this.client.logger.debug('closed'),
message: onMessage,
});
}
}
export default class B {
account;
constructor() {
this.account = new A(this.didReceiveMessage.bind(this));
// or inline:
this.account = new A(data => {
console.log(data); // or whatever
});
}
didReceiveMessage(data) {
console.log(data); // or whatever
}
}
I am not an expert on NodeJs, but you can use something like an emitter plugin.
In javascript, it would look like this:
function A() {
Emitter(this);
this.action = function() {
console.log("something happened");
this.emit("action", { prop: "value" });
};
}
function B(a_instance) {
// subscribe to "action" event
a.on("action", function(data) {
console.log(data.prop); // "value"
});
};
var myA = new A();
var myB = new B(myA);
myA.action();

Javascript refactoring boilerplate into decorators for chat app event handler

I'm writing an event handler class for a messaging app, and I have some code that looks like so:
class EventHandler {
constructor(client, chatroomManager) {
this.client = client;
this.chatroomManager = chatroomManager;
}
handleJoin(roomId, cb) {
...
}
handleLeave(roomId, cb) {
if (!this.client.isLoggedIn()) {
cb("err");
return;
}
const chatroom = this.chatroomManager.getRoomById(roomId);
// check that chatroom exists
if (chatroom != null) {
// check that user was already in the room
if (chatroom.getRoomId() in this.client.rooms) {
chatroom.removeUser(this.client);
} else {
cb("error")
}
} else {
cb("error");
}
}
handleMessage(roomId, message, cb) {
if (!this.client.isLoggedIn()) {
cb("err");
return;
}
const chatroom = this.chatroomManager.getRoomById(roomId);
// check that chatroom exists
if (chatroom != null) {
// check that user was already in the room
if (chatroom.getRoomId() in this.client.rooms) {
chatroom.addEntry(this.client.username, message);
} else {
cb("error")
}
} else {
cb("error");
}
}
}
As you can see, there is a lot of repeated code in the handleLeave and handleMessage methods (and even more methods in the full code). I want to get rid of this as much as possible.
First of all, I expect that I might be able to replace the first if statement with an ES7 decorator, like so:
class EventHandler {
...
#requirelogin
handleLeave(roomId, cb) {
const chatroom = this.chatroomManager.getRoomById(roomId);
// check that chatroom exists
if (chatroom != null) {
// check that user was already in the room
if (chatroom.getRoomId() in this.client.rooms) {
chatroom.removeUser(this.client);
} else {
cb("error")
}
} else {
cb("error");
}
}
}
Secondly, I think that if I can refactor the chatroomManager.getRoomById method to return a Promise, then I can define a helper function:
validateRoom(roomId) {
return chatroomManager.getRoomById(roomId).then(room => {
if (roomId in this.client.rooms) {
return room;
} else {
throw new Error("Client is not in room");
}
}
}
Then I can refactor like so:
class EventHandler {
...
#requirelogin
handleLeave(roomId, cb) {
this.validateRoom(roomId)
.then(room => room.removeUser(this.client))
.catch(cb);
}
}
I'm content with this, but I wonder if it can be simplified even further? It seems like this pattern of
this.validateRoom(roomId)
.then(room => { /* do something */ })
.catch(cb);
will still be repeated many times, and I wonder if there is a clean way to extract this (perhaps as another decorator, although I can't see how)? Ideally I would like to have a setup where the public API of EventHandler stays the same
handleLeave(roomId, cb)
handleMessage(roomId, message, cb)
but under the hood I am implementing something like
handleLeave(room) {
room.removeUser(this.client);
}
handleMessage(room, message) {
room.addEntry(this.client.username, message);
}
Is there a way to accomplish this? Would I need to do any large refactoring to the code in order to do so?

How can I call a created object as a function in JS

I have a class Notify, for example:
class Notify {
success(text) {
// TODO
}
error(text) {
// Todo
}
}
export default new Notify();
When I'm using I call directly to methods in this class like Notify.success(), So, now I want to try a new way to call it like Notify('my title', 'success'). In PHP I know it's __invoke method, but in JS I don't know how to use it likes that. Can I do that in class? Or I have to use 'normally' functions.
Please help me. Thank you.
There is no analogue of __invoke in JavaScript. You can make a function and then attach properties to is so that it can be used as an object too.
function notifyConstructor() {
// Make the function
const notify = (text, type) => {
switch (type) {
case 'success': return notify.success(text);
case 'error': return notify.error(text);
default: throw TypeError(`Unknown type "${type}"`);
}
};
// Attach public properties and methods to the function
notify.success = text => {
// TODO
};
notify.error = text => {
// Todo
};
return notify;
}
const notify = notifyConstructor(); // Make a Notify instance
notify('my title', 'success'); // Call the instance
notify.success('my title'); // Call an instance method
You can get the functions using the context this as follows.
Be careful if you call the function invoke by itself (Stackoverflow error).
class Notify {
constructor() {
}
invoke(msg, fn) {
if (this[fn]) {
this[fn](msg);
} else throw new Error("Illegal argument Error.");
}
success(text) {
console.log('Success:', text);
}
error(text) {
console.log('Error:', text);
}
}
let notify = new Notify();
notify.invoke('my title', 'success');
notify.invoke('my title', 'error');
notify.invoke('my title', 'ele');
Or, directly over the instantiated object:
class Notify {
constructor() {
}
success(text) {
console.log('Success:', text);
}
error(text) {
console.log('Error:', text);
}
}
let notify = new Notify();
notify['success']('my title');
notify['error']('my title');
In javascript object property access using a dot (.) is short hand notation for accessing the property using array notation ([]). Short hand notation does, however, require the property name to conform to the syntax used for identifiers.
Assuming Notify is the name of the imported instance,
Notify["success"]("my title"]
is equivalent to
Notify.success( "my title")

What's an angular lifecycle-hook need when changing sets in componentInstance property by service?

I have a component that i send to MdDialog(Angular Material Dialog in my custom service.ts)
dialogRef = this.dialog.open(component, config);
And when I change a public property of this component by componentInstance like that:
dialogRef.componentInstance.task = task;
Angular shows me an error:
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'dialog'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook ?
Full code of open-modal.service.ts
#Injectable()
export class TasksPopupService {
constructor(
private dialog: MdDialog,
private router: Router,
private tasksService: TasksService
) { }
public open(component: any, id?: string) {
if (id) {
this.tasksService.find(id)
.subscribe(task => {
this.bindDialog(component, task);
});
} else {
this.bindDialog(component, new Task());
}
}
bindDialog(component, task: Task) {
let dialogRef;
let config = new MdDialogConfig();
config.height = '80%';
config.width = '70%';
dialogRef = this.dialog.open(component, config);
dialogRef.componentInstance.task = task;
dialogRef.afterClosed().subscribe(res => {
this.router.navigate([{ outlets: { popup: null } }], { replaceUrl: true });
});
return dialogRef;
}
}
But an error occured only if id is undefined (in ELSE block) I think it's because of this.tasksService.find return Observable (async), and block ELSE is not async. But I'm not sure.
I has some confuse becouse error eccured in MdContainer of Angular Material.
If i get data from server it's need some time, but when i pass a new object it's occur fast and change detection is not finished if i understend right.
Also, it's not parent/child component and lifecycle hooks maybe not works as we expect.
I found solution, but it's not right. Just fast solution.
if (id) {
this.tasksService.find(id)
.subscribe(task => {
this.bindDialog(component, task);
});
} else {
Observable.of(new Task()).delay(300).subscribe(task => {
this.bindDialog(component, task);
});
}
I use delay for change detection has finished and error will not throw.

Categories

Resources