-
Notifications
You must be signed in to change notification settings - Fork 834
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
Add Graceful Shutdown Support #1321
Conversation
private _collect() { | ||
this._meter.collect(); | ||
shutdown() { | ||
this._collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does shutdown call an async collect but not await it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you give an example of when this could have negative side effects? I was under the impression that async functions could continue to run even if the parent function had finished.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not that it will have a negative side-effect, but that some users may want to manually call shutdown and wait for it to finish.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can make shutdown async as well and add awaits so users can wait for it to finish if they call it manually.
|
||
shutdown(): void { | ||
if (this._config['exporter']) { | ||
this._config['exporter'].shutdown(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe exporter shutdown takes a callback param? how will we know when its done?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this another case where a user may want to call shutdown manually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. I can't think of a reason not to support the use-case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Metric exporter does not take a callback param currently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm bummer
@@ -47,6 +47,9 @@ export class BasicTracerProvider implements api.TracerProvider { | |||
logger: this.logger, | |||
resource: this.resource, | |||
}); | |||
if (this._config['gracefulShutdown']) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why bracket notation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I usually program in C/C++, I am not attached to any one notation and if the project does not use this I have no issue switching. Thanks for bringing this to my attention!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typically the bracket notation in typescript means you are accessing a private property
@@ -99,4 +102,8 @@ export class BasicTracerProvider implements api.TracerProvider { | |||
api.propagation.setGlobalPropagator(config.propagator); | |||
} | |||
} | |||
|
|||
shutdown(): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no callback? not waiting for the shutdown to finish?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had implemented all of this assuming users would not be calling shutdown manually. I can add a callback parameter if you think that would be a good feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by "not waiting for the shutdown to finish"? I assumed shutdown was a blocking function. I added a callback option which will be passed to the shutdown function in the processor but do I need to make this async and add an await or should that be enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue with adding the callback option in these shutdown methods: any function used as a signal handler cannot have any args so adding callbacks is a bit tricky. We can get around this by adding @ts-ignore to ignore strict function typing but this may be ill advised? I compiled it using this and the test cases worked fine. Another solution I could try would be to have a private onShutdown function which is bound to the signal handler and a public shutdown function which accepts a callback - they would have identical functionality aside from the callback arg but this may look confusing as it requires duplicated code (I wish typescript had overloaded functions!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am strongly against ts-ignore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up not using @ts-ignore, I also added callbacks and make the meter provider shutdown blocking.
Codecov Report
@@ Coverage Diff @@
## master #1321 +/- ##
==========================================
+ Coverage 94.12% 94.21% +0.09%
==========================================
Files 145 146 +1
Lines 4341 4377 +36
Branches 886 893 +7
==========================================
+ Hits 4086 4124 +38
+ Misses 255 253 -2
|
Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
process
is not supported in web environments, which means this PR will make the meter provider not web compatible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The process is not part of browser and MeterProvider.ts
is meant to be used in both environment, so you can't use it like this. If you want to implement mechanism that will execute something when process is closed you should mimic the same behaviour in browser (probably event unload on window). But to do that you should create some platform dependent mechanism. Put in inside core sdk in folder platform and create some easy to share function for example
// platform/browser/ShutdownNotifier.ts
export function onGlobalShutdown(callback) {
window.addEventListener('unload', callback);
}
// platform/node/ShutdownNotifier.ts
export function onGlobalShutdown(callback) {
process.once('SIGTERM', callback);
}
//and then in your code
onGlobalShutdown(function(){
this._shutdownAllMeters.bind(this)
}.bind(this));
@obecny Hopefully this addresses your concerns. |
@@ -75,7 +75,7 @@ export class MeterProvider implements api.MeterProvider { | |||
if (this._config.exporter) { | |||
this._config.exporter.shutdown(); | |||
} | |||
await Promise.all( | |||
return Promise.all( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is fine, but you can remove async now from the function _shutdownAllMeters
as nothing is async anymore here
if (result !== ExportResult.SUCCESS) { | ||
// @todo: log error | ||
} | ||
this._exporter.export(this._meter.getBatcher().checkPointSet(), result => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this._exporter.export(this._meter.getBatcher().checkPointSet(), result => { | |
return new Promise((resolve, reject) => { | |
this._exporter.export( | |
this._meter.getBatcher().checkPointSet(), | |
result => { | |
if (result === ExportResult.SUCCESS) { | |
resolve(); | |
} else { | |
reject(); | |
} | |
} | |
); | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a follow up PR which fixes the remaining issues but also unifies and fixes the shutdown across all exporters, metric, spans, processors, so that the shutdown will be correctly handled in whole pipeline. It will also return promise that will resolve correctly when all tasks that depend on it will resolve including http, xmlhttprequest, grpc. Callbacks has been replaced with Promises.
@open-telemetry/javascript-approvers please have a look at follow up PR ^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good. Sorry for the long review process
No worries, thanks for all the help and feedback along the way! How many more reviewers do I need for this PR? |
One more |
@dyladan have you seen #1321 (review) ? |
Yes I saw it but I assumed since you opened it against jonah's master it wasn't ready for reviews and that you would open a PR here when you were ready. |
Well I can't open the PR against otel as it requires this PR, otherwise it will show changes from here and mine changes and it will be impossible to see differences. |
@obecny A common pattern is to open a draft PR against otel anyway, and point out that only the last commit is subject to review. Then just rebase and force-push once this PR is merged. Not ideal, but it offers good visibility. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall LGTM.
@@ -39,11 +39,15 @@ export interface MeterConfig { | |||
|
|||
/** Metric batcher. */ | |||
batcher?: Batcher; | |||
|
|||
/** Bool for whether or not graceful shutdown is enabled */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to highlight the pitfall of disabling the gracefulShutdown
option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a little more emphasis about why it's good to enable graceful shutdown.
// reset spans in memory. | ||
memoryExporter.reset(); | ||
beforeEach(() => { | ||
memoryExporter = new InMemorySpanExporter(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious to know the reasoning behind this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only having one instantiation of the in-memory exporter caused some strange bugs in testing. This approach resolves the bug and doesn't complicate the test case too much.
@@ -214,7 +214,6 @@ describe('BatchSpanProcessor', () => { | |||
it('should call an async callback when shutdown is complete', done => { | |||
let exportedSpans = 0; | |||
sinon.stub(exporter, 'export').callsFake((spans, callback) => { | |||
console.log('uh, export?'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
I think this can be merged, but I will let @obecny do it as he seems to have a plan for follow up. |
Graceful shutdown support was removed via open-telemetry#1522 Remove the corresponding configuration because it is not used anymore. Refs: open-telemetry#1321
Graceful shutdown support was removed via open-telemetry/opentelemetry-js#1522 Remove the corresponding configuration because it is not used anymore. Refs: open-telemetry/opentelemetry-js#1321
Graceful shutdown support was removed via open-telemetry/opentelemetry-js#1522 Remove the corresponding configuration because it is not used anymore. Refs: open-telemetry/opentelemetry-js#1321
* chore(mongo): add DB_OPERATION attribute * chore(mongo): replace 'remove' with 'delete' * chore(mongo): add tests * chore(mongo): add condition in case cmd=unknown * chore(mongo): make code more readable
Which problem is this PR solving?
Short description of the changes
collect
method to wait for collection to happen before export (I think this may fix an undocumented bug?)