Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Only initailize the SDK once. #368

Merged
merged 9 commits into from
Jan 11, 2023
Merged

fix: Only initailize the SDK once. #368

merged 9 commits into from
Jan 11, 2023

Conversation

ivarconr
Copy link
Member

About the changes

Before this fix all calls to initialize would create a new SDK instance, all maintaining the connection towards the Unleash API. We have observed this causing accidental DDosing of the Unleash API.

This fix will make sure that the SDK is only initialized once (aka singleton) when using the initialize method.

Discussion points

Unsure if we should warn when the Unleash SDK has been initialized more than 10 times.

Unleash.instance = this;
}

static getInstance(config: UnleashConfig) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introducing the singleton pattern here.

@coveralls
Copy link

coveralls commented Sep 26, 2022

Coverage Status

Coverage: 92.093% (-0.2%) from 92.25% when pulling 985b9d7 on fix/initalize-once into 52e3c41 on main.

}
instance = new Unleash(options);
instance.on('error', () => {});
instance = Unleash.getInstance(options);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function was never intended to be used to create multiple unleash SDK instances. The old (replaced) implementation is quite strange, as they keep the instances around (in-memory) but all functions will always be moved to the fresh instance. To me this smells like a bug.

Copy link
Contributor

@thomasheartman thomasheartman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work and good call 🙌🏼 I've got a couple questions regarding how we should use the singleton inline plus some minor suggestions on wordings.

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
@@ -67,7 +67,7 @@ const unleash = new Unleash({

unleash.on('ready', console.log.bind(console, 'ready'));

// required error handling when using unleash directly
// optional error handling when using unleash directly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is nice 😄

src/unleash.ts Outdated
Comment on lines 204 to 208
if(Unleash.instance) {
return Unleash.instance;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you call getInstance with a different config, does it make sense to update the config for the instance? Or is that just confusing? It feels a little misleading that you can pass in the config as an argument, but that it does nothing. That said, I'm not sure there is a better way? Could we make it parameterless somehow and have you pass config in other ways? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I think that you are spot on the getInstance() method in the singleton pattern usually does not take arguments (options). I am a bit unsure how/where to communicate the options in a clear and concise way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good point. If you remove the need completely and use default values, then I imagine almost everyone will just not configure the instance at all.

Copy link
Member

@sighphyre sighphyre Oct 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without looking at the docs, I would probably read this as a multiton implementation. What about something like:

static buildInstance(options?: UnleashConfig){
  if(!options){
     Unleash.singletonConfig = makeDefaultOptions();
  }else {
     Unleash.singletonConfig = options
  }
}

static getInstance() {
  if(!Unleash.singletonConfig){
    throw new Error("You gotta call buildInstance first")
  }
  //setup and/or return instance
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you always makes me learn something new @sighphyre, thanks!

README.md Outdated Show resolved Hide resolved
src/unleash.ts Outdated
Comment on lines 204 to 208
if(Unleash.instance) {
return Unleash.instance;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good point. If you remove the need completely and use default values, then I imagine almost everyone will just not configure the instance at all.

src/unleash.ts Outdated
Comment on lines 96 to 99
if(Unleash.instanceCount > 10) {
console.warn('The unleash SDK has been initialized more than 10 times')
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding whether we should warn or not. I'm into the idea, but maybe make it configurable? For instance, add a disableInstanceCountWarning variable that can turn it off or something?

Also, should it be console.warn or a configurable logger? I don't know how you usually do that in Node, but I'd expect to be able to redirect warnings to where I'd want them, I think. Also, if it's a configurable logger (or just a logWarning: (warning: string) => void function, then you could pass () => {} as an argument and disable the logging that way 🤷🏼

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. We currently do not support a log abstraction, and I think it would be more work to get that in than I am willing to do for this change. We do have the event emitter, but the purpose here is to want users of the SDK that something is off.

A suitable middle-ground would be to add the option to disable the warning entirely, and log it as an error to the event emitter, which default will console.error it, and the user can override that by registering an on("error") handler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more work to get that in than I am willing to do for this change

Aye, I agree with this.

A suitable middle-ground would be to add the option to disable the warning entirely, and log it as an error to the event emitter, which default will console.error it, and the user can override that by registering an on("error") handler.

Just to make sure I understand this: does it only log to the event emitter if you disable the warning, or does it log to the event emitter regardless? And do we have support "warning" events? This isn't an error, so logging it as such feels a little icky to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure I understand this: does it only log to the event emitter if you disable the warning

No, the plan is then to skip the error entirely,

@thomasheartman
Copy link
Contributor

It's been a little while: are we still working on this PR or should we close it or otherwise?

@stale
Copy link

stale bot commented Dec 1, 2022

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Dec 1, 2022
@stale stale bot closed this Dec 11, 2022
@ivarconr ivarconr reopened this Jan 2, 2023
@stale stale bot removed the stale label Jan 2, 2023
ivarconr and others added 4 commits January 10, 2023 16:23
Before this fix all calls to `initalize` would create a new
SDK instance, all maintaining the connection towards the
Unleash API. We have observed this causing accidental DDosing of
the Unleash API.

This fix will make sure that the SDK is only initalized once
(aka singleton) when using the `initalize` method.
Co-authored-by: Thomas Heartman <thomas@getunleash.ai>
Co-authored-by: Thomas Heartman <thomas@getunleash.ai>
Co-authored-by: Thomas Heartman <thomas@getunleash.ai>
examples/simple_usage.js Outdated Show resolved Hide resolved
src/unleash.ts Outdated
Unleash.instanceCount++;

this.on(UnleashEvents.Error, (err) => {
console.error(err?.message);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to check if there are other event handlers registered and only console.error if no others have registered.

src/unleash.ts Outdated
});

if(!skipInstanceCountWarning && Unleash.instanceCount > 10) {
const error = new Error('The unleash SDK has been initialized more than 10 times');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to wrap this in a process.nextTick() to ensure using code have a chance to register an event listener before it triggers.

src/unleash.ts Outdated

private static instance?: Unleash;

private static instanceCount: 0;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is a very nice bug, I accidentally set the type to "0" which is definetley not what I tried to do here!

Copy link
Member

@sighphyre sighphyre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, looks good to me

static getInstance(config: UnleashConfig) {
const configSignature = generateHashOfObject(config);
if(Unleash.instance) {
if(configSignature !== Unleash.configSignature) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty elegant, I like it

@ivarconr ivarconr merged commit bff4ad0 into main Jan 11, 2023
@ivarconr ivarconr deleted the fix/initalize-once branch January 11, 2023 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

None yet

4 participants