agsh / onvif

ONVIF node.js implementation
http://agsh.github.io/onvif/
MIT License
693 stars 236 forks source link

Fix EventEmitter global to all Cam objects. #137

Closed bl0ggy closed 4 years ago

bl0ggy commented 4 years ago

Using util.inherits makes EventEmitter global to all Cam objects. It is recommended to extend instead of using util.inherit, which fixes the problem, but it is not possible as we don't use classes. This pull request fixes that by instantiating an EventEmitter in the Cam constructor and creating on and emit functions. I also added a condition on listeners in _eventRequest to avoid _eventRequest/_eventPull/pullMessages loop when we unsubscribe while waiting for a pullMessages response (which is almost always the case). The listeners are also removed in unsubscribe because we could never subscribe anymore as we create a PullPointSubscription only if there is no listeners.

The code below shows that the EventEmitter is global. Also include a console.log of the listeners length here, right after Cam.prototype.on('newListener', function(name) { to see how it increases as you add more cameras.

const Cam = require('onvif').Cam;

let c1 = new Cam({
    hostname: '192.168.1.101',
    username: 'admin',
    password: 'admin',
},function(res) {
    console.log(res);
    c1.on('event', function (data) {
        console.log('c1 event', data);
    });
});
let c2 = new Cam({
    hostname: '192.168.1.102',
    username: 'admin',
    password: 'admin',
},function(res) {
    console.log(res);
    c2.on('event', function (data) {
        console.log('c2 event', data);
    });
});

To test the loop, keep only one camera with on('event'), then add a setTimeout() that will unsubscribe the camera after a while, and add console.logs at the begining of functions _eventRequest, _eventPull and pullMessages.

coveralls commented 4 years ago

Coverage Status

Coverage decreased (-0.6%) to 86.708% when pulling be39c3d7924ed5a2d748357193df8c061cc97ece on bl0ggy:fix_static_EventEmitter into 2a7f9fe267058e8d069253f7d1189e5744b09470 on agsh:master.

RogerHardiman commented 4 years ago

Just a thought (and I've not had much time to look at this). But should we convert the Cam object into a proper JS Class?

bl0ggy commented 4 years ago

That would be better of course, but that would break the code of people using NodeJS below v6. Maybe for version 1.0.0 of this repository.

RogerHardiman commented 4 years ago

Have invited Andrew and Chris to review

brilthor commented 4 years ago

@bl0ggy I'm seeing an issue with the current codebase where when multiple cam objects are created and I register to listen to each using the cam.on('event', ... after a time only one remains in the polling loop. Is this what is fixed by this MR or is that an unrelated issue?

RogerHardiman commented 4 years ago

Hi all and especially to @bl0ggy and @brilthor @bl0ggy has done lots of Pull Requests which all look good and make lots of improvements. I've been so busy recently since the lockdown in the UK that I've not had time to go through and merge them all yet. Sorry about that.

brilthor commented 4 years ago

Answering my own question above: from some initial testing it does look like this fixes the issue I described

bl0ggy commented 4 years ago

@brilthor Yes this PR fixes the issue you explain. The problem is that the EventEmitter is common to all Cam objects. When you try to listen to event with one camera, we count how many listeners are listening to that event, if the count is 0 we create a PullPointSubscription. So when you try to listen to event with another camera, the count will not be 0 anymore as the EventEmitter is the same for all cameras, so no PullPointSubscription is created anymore.

RogerHardiman commented 4 years ago

Many thanks for the change and the PR. Sorry it took a while to merge.

agsh commented 4 years ago

@bl0ggy @RogerHardiman Hi! And very big sorry for the late reply and I'll maybe revert this merge.

This is definitely my huge mistake to wrote this in events.js:

Cam.prototype.on('newListener', () => { ... });

And you are absolutely right in putting subscription to the newListener event into the constructor. And this is the bug point.

But are you sure, that we need to add eventEmitter property? IMHO we can refuse it and everything should work ok. Maybe also add EventEmitter.call(this); at the top of the constructor.

RogerHardiman commented 4 years ago

Hi @agsh Thanks for the feedback. I had a quick look at the PR, ran a test and accepted the merge after there was a 2nd user hitting the bug.

So if you need to revert and change the code, then go ahead.

agsh commented 4 years ago

@brilthor @bl0ggy @RogerHardiman I think, I found a better solution. Can you please check and review this PR https://github.com/agsh/onvif/pull/161? If it works and fixes the problem with two and more cams it'll be great!