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

Variable strength aborting #1215

Open
bojavou opened this issue Jul 5, 2023 · 1 comment
Open

Variable strength aborting #1215

bojavou opened this issue Jul 5, 2023 · 1 comment

Comments

@bojavou
Copy link

bojavou commented Jul 5, 2023

Some systems distinguish between a graceful shutdown and a forceful termination. This distinction cannot be delivered through AbortController. It would be nice if the same channel could be used to deliver both.

The current interface might extend to this cleanly, without breaking anything. The delivered Event could provide a boolean .force indicating whether a forceful abort was requested or not. AbortController.prototype.abort() could take the option { force: true } to request a forceful abort. A second call to abort() could be allowed, for requesting forceful abort after a standard abort was already requested.

2 levels of strength seem sufficient. I've never seen a use for further distinctions.

Some usage patterns.

  • Request forceful abort immediately. Everything is terminated as quickly as possible.

This might be tied to a more forceful user control, eg Kill vs Cancel.

const reason = new Error('User clicked Kill Now')
controller.abort(reason, { force: true })
const reason = new Error('User clicked Cancel')
controller.abort(reason)
function aborted (event) {
  if (event.force) {
    kill()
  } else {
    cancel()
  }
}
  • Request standard abort with a time limit. Request forceful abort if it times out.

This might be useful where closing a connection can take a long time. The close logic can have retries to tolerate transient network errors, with delays between them. If the process drags on too long, terminate.

process.on('SIGINT', () => {
  // Await close of everything watching the signal
  Promise.allSettled(clients.map(client => client.done))
    .then(() => { process.exit() })

  // Request standard shutdown
  controller.abort()

  // Time out standard shutdown in 5 seconds
  // Unref the timer to prevent it keeping the process alive
  const timer = setTimeout(() => {
    const reason = new Error('Closing connection timed out')
    controller.abort(reason, { force: true })
  }, 5000)
  timer.unref()
})
  • Offer users 2 levels of interrupt.

This is a common pattern in the CLI interface of servers. The first CTRL+C initiates a graceful shutdown: the server stops accepting new connections and waits for existing connections to complete normally. A second CTRL+C during graceful shutdown terminates all open connections without waiting for completion and shuts down immediately.

$ ./server
Serving 112 requests
Interrupt received, shutting down
Completed 52/112 requests
Second interrupt received, terminating open connections
let shuttingDown = false
process.on('SIGINT', () => {
  if (!shuttingDown) {
    // First interrupt, start graceful shutdown
    shuttingDown = true
    program.closed.then(() => { process.exit() })
    controller.abort()
  } else {
    // Second interrupt, kill everything now
    controller.abort(undefined, { force: true })
  }
})
@keithamus
Copy link

You can attach data to the Error class:

const reason = new Error('User clicked Kill Now')
reason.force = true

function aborted (event) {
  if (event.target.reason.force) {
    kill()
  } else {
    cancel()
  }
}

Or use two signals:

let shuttingDown = false
process.on('SIGINT', () => {
  if (!shuttingDown) {
    // First interrupt, start graceful shutdown
    shuttingDown = true
    program.closed.then(() => { process.exit() })
    gracefulController.abort()
  } else {
    // Second interrupt, kill everything now
    forcefulController.abort()
  }
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants