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

Add Graceful Shutdown Support #1321

Merged
merged 69 commits into from
Aug 17, 2020
Merged

Conversation

jonahrosenblum
Copy link
Contributor

Which problem is this PR solving?

Short description of the changes

  • Add event handlers to both Trace & Meter providers to call shutdown methods when the process receives a SIGTERM.
  • Add unit tests to demonstrate how the shutdown methods are called and that exports are successful
  • Modify push controller's collect method to wait for collection to happen before export (I think this may fix an undocumented bug?)
  • Modify config file to allow graceful shutdown to be turned off, by default it is enabled.

packages/opentelemetry-metrics/src/MeterProvider.ts Outdated Show resolved Hide resolved
packages/opentelemetry-metrics/src/MeterProvider.ts Outdated Show resolved Hide resolved
private _collect() {
this._meter.collect();
shutdown() {
this._collect();
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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();
Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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']) {
Copy link
Member

Choose a reason for hiding this comment

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

why bracket notation?

Copy link
Contributor Author

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!

Copy link
Member

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 {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

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

I do

Copy link
Contributor Author

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?

Copy link
Contributor Author

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!)

Copy link
Member

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

Copy link
Contributor Author

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
Copy link

codecov bot commented Jul 16, 2020

Codecov Report

Merging #1321 into master will increase coverage by 0.09%.
The diff coverage is 78.57%.

@@            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     
Impacted Files Coverage Δ
packages/opentelemetry-metrics/src/types.ts 100.00% <ø> (ø)
packages/opentelemetry-tracing/src/config.ts 100.00% <ø> (ø)
...lemetry-core/src/platform/node/ShutdownNotifier.ts 33.33% <33.33%> (ø)
...ges/opentelemetry-metrics/src/export/Controller.ts 76.19% <60.00%> (+9.52%) ⬆️
...ackages/opentelemetry-metrics/src/MeterProvider.ts 96.55% <92.85%> (-3.45%) ⬇️
packages/opentelemetry-metrics/src/Meter.ts 96.00% <100.00%> (+0.08%) ⬆️
...s/opentelemetry-tracing/src/BasicTracerProvider.ts 81.39% <100.00%> (+4.92%) ⬆️
...es/opentelemetry-tracing/src/MultiSpanProcessor.ts 100.00% <0.00%> (+9.09%) ⬆️
...elemetry-tracing/src/export/ConsoleSpanExporter.ts 100.00% <0.00%> (+15.38%) ⬆️
... and 2 more

Copy link
Member

@dyladan dyladan left a 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

Copy link
Member

@dyladan dyladan left a 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

Copy link
Member

@obecny obecny left a 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));

@jonahrosenblum
Copy link
Contributor Author

@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(
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 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 => {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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();
}
}
);
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@obecny I implemented this while also updating the return type and leaving in the @todo comment, I think there is still error logging functionality that needs to be handled so we should likely leave that in, no?

Copy link
Member

@obecny obecny left a 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.

jonahrosenblum#3

@open-telemetry/javascript-approvers please have a look at follow up PR ^^

Copy link
Member

@dyladan dyladan left a 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

@jonahrosenblum
Copy link
Contributor Author

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?

@dyladan
Copy link
Member

dyladan commented Aug 14, 2020

One more

@obecny
Copy link
Member

obecny commented Aug 14, 2020

@dyladan have you seen #1321 (review) ?

@dyladan
Copy link
Member

dyladan commented Aug 14, 2020

@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.

@obecny
Copy link
Member

obecny commented Aug 14, 2020

@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.

@kintel
Copy link

kintel commented Aug 14, 2020

@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.

Copy link
Member

@mayurkale22 mayurkale22 left a 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 */
Copy link
Member

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.

Copy link
Contributor Author

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();
Copy link
Member

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?

Copy link
Contributor Author

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?');
Copy link
Member

Choose a reason for hiding this comment

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

Good catch!

@dyladan
Copy link
Member

dyladan commented Aug 17, 2020

I think this can be merged, but I will let @obecny do it as he seems to have a plan for follow up.

@obecny obecny merged commit b884eec into open-telemetry:master Aug 17, 2020
@dyladan dyladan linked an issue Aug 20, 2020 that may be closed by this pull request
@Flarna Flarna mentioned this pull request Sep 12, 2020
Flarna added a commit to dynatrace-oss-contrib/opentelemetry-js that referenced this pull request Nov 19, 2020
Graceful shutdown support was removed via
open-telemetry#1522

Remove the corresponding configuration because it is not used anymore.

Refs: open-telemetry#1321
dyladan pushed a commit that referenced this pull request Nov 25, 2020
Graceful shutdown support was removed via
#1522

Remove the corresponding configuration because it is not used anymore.

Refs: #1321
SuperButterfly pushed a commit to SuperButterfly/opentelemetry-js that referenced this pull request Sep 18, 2022
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
viridius-1 pushed a commit to viridius-1/opentelemetry-js that referenced this pull request Jan 14, 2023
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
pichlermarc pushed a commit to dynatrace-oss-contrib/opentelemetry-js that referenced this pull request Dec 15, 2023
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cloud Run Graceful Termination Support