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 #604

Merged
merged 19 commits into from
Oct 4, 2022
Merged

add graceful shutdown #604

merged 19 commits into from
Oct 4, 2022

Conversation

silesky
Copy link
Contributor

@silesky silesky commented Sep 29, 2022

Graceful Shutdown

Avoid losing events on exit!

  • Call .closeAndFlush() to stop collecting new events and flush all existing events.
  • If a callback on an event call is included, this also waits for all callbacks to be called, and any of their subsequent promises to be resolved.
await analytics.closeAndFlush()
// or
await analytics.closeAndFlush({ timeout: 5000 }) // force resolve after 5000ms

Graceful Shutdown: Advanced Example

import express from 'express'
const app = express()

const server = app.listen(3000)
app.get('/', (req, res) => res.send('Hello World!'));

const onExit = async () => {
   console.log("Gracefully closing server...");
   await analytics.closeAndFlush() // flush all existing events
   server.close(() => process.exit());
 };

process.on("SIGINT", onExit);
process.on("SIGTERM", onExit);

https://github.com/segmentio/analytics-next/blob/9d021e2f3ea78099dd12ec50622483f945267250/packages/node/README.md

@changeset-bot
Copy link

changeset-bot bot commented Sep 29, 2022

⚠️ No Changeset found

Latest commit: 4614ebf

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@silesky silesky requested a review from chrisradek September 29, 2022 03:35
@silesky silesky force-pushed the add-graceful-shutdown branch from f91dcc5 to 1ebb284 Compare September 29, 2022 03:36
@silesky silesky requested a review from pooyaj September 29, 2022 04:10
@silesky silesky marked this pull request as draft September 29, 2022 06:05
@silesky silesky marked this pull request as ready for review September 29, 2022 17:29
@silesky silesky force-pushed the add-graceful-shutdown branch from a9d3b27 to 2035675 Compare September 29, 2022 17:44
Copy link
Contributor

@chrisradek chrisradek left a comment

Choose a reason for hiding this comment

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

Overall I think this looks good - just had some minor questions.

})
}

function sleep(timeoutInMs: number): Promise<void> {
export function sleep(timeoutInMs: number): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ha, didn't realize we had sleep defined here too. In the browser package we had it in lib/sleep.ts as well so wouldn't need it defined it its version of this core file like we do currently 😄

Copy link
Contributor Author

@silesky silesky Sep 29, 2022

Choose a reason for hiding this comment

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

yes, we define sleep everywhere!
I figured we would make core have the canonical version and remove the other ones — thoughts? Or maybe we create a utils package.

Copy link
Contributor

Choose a reason for hiding this comment

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

That sounds good to me!

timeout,
}: {
/** Set a maximum time permitted to wait before resolving. Default = no maximum. */
timeout?: number
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of scope for this feature, but it'd be awesome if we could also tear down any pending tasks (setTimeouts and promises) that would otherwise prevent a process from exiting when we reach this timeout. (Bigger task since that means supporting something like abortSignal throughout our codebase)

Copy link
Contributor Author

@silesky silesky Sep 30, 2022

Choose a reason for hiding this comment

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

Interesting, but not sure if this is needed, have you dealt with this in similar libs?

Thinking this through: If we do force resolve and there are stalled HTTP Requests to the analytics API, they wouldn't be connected to promises whose resolution was blocking an end-user network request, since our lib is very "fire-and-forget" --- apps send events passively/asynchronously and their APIs don't wait for our response before serving their users. Like a logging library.

Like, If there is a SIGTERM and something in their network or ours had gone wrong, I'm not sure what the practical difference is between terminating a stalled request to our analytics HTTP API via abort vs allowing node to do it during process.exit() (if we're talking about a client, it would be "Socket hang up" vs "The socket connection was aborted.").

(AFAIK, typical pending async callbacks in the task queue like those registered in setTimeout have no effect on server.close).

Maybe we can talk about this offline if we want to get into the weeds? I created an example app.

private _dispatch(segmentEvent: CoreSegmentEvent, callback?: Callback) {
if (this._isClosed) {
return undefined
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to surface an error to the user when this happens letting them know they tried sending events after closing perhaps similar to how we emit delivery failures today? (Though I don't think you have the context at this point so not sure how difficult that would be)

Copy link
Contributor Author

@silesky silesky Sep 29, 2022

Choose a reason for hiding this comment

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

Good idea. I think we could emit an event or an event error with a code like "called_after_closing" here. That way, a consumer could log them or even store them in a DB for future rehydration.

@silesky silesky force-pushed the add-graceful-shutdown branch from 3378c84 to e695bf8 Compare September 30, 2022 03:57
@silesky silesky requested a review from chrisradek September 30, 2022 20:48
@silesky silesky force-pushed the add-graceful-shutdown branch from 7fc6fb2 to bb72c45 Compare September 30, 2022 20:53
@silesky silesky force-pushed the add-graceful-shutdown branch 2 times, most recently from 64fab5d to 87a0ff2 Compare September 30, 2022 21:07
@silesky silesky force-pushed the add-graceful-shutdown branch from 87a0ff2 to 8d59e3f Compare September 30, 2022 21:15
Copy link
Contributor

@chrisradek chrisradek left a comment

Choose a reason for hiding this comment

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

Looks good to me, :shipit:

@silesky silesky merged commit a23ccee into master Oct 4, 2022
@silesky silesky deleted the add-graceful-shutdown branch October 4, 2022 16:25
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.

2 participants