pigoz/effect-crashcourse

Some more feedback

Opened this issue · 4 comments

  1. Wouldn’t use “stupid” here but rather “naive” or “unsafe”

export const useFileDescriptorStupid: useFileDescriptor = Effect.gen(function* (

  1. “trivial” or “pointless” rather than “stupid”:

// These are stupid Layers with no lifetime

  1. The following bit seems a bit out of place and unclear. Might be better to remove or move to next section. Could also maybe be rephrased if it’s meant to serve as a transition to next section

* But the point of Layers is to define application wide resources.

SIGINT and SIGTERM would be more appropriate here:

process.on("beforeExit", () => Effect.runPromise(close));

this gist gives a nice overview of various node events related to process shutting down:

https://gist.github.com/hyrious/30a878f6e6a057f09db87638567cb11a

pigoz commented

this gist gives a nice overview of various node events related to process shutting down

TIL! (I'm new to Node)

I used beforeExit because that's the only one that handles promises IIRC. I wonder how I would integrate async code with the other handlers.

pigoz commented

I did address your suggestions. Still unsure how to go about the beforeExit issue.

I used beforeExit because that's the only one that handles promises IIRC. I wonder how I would integrate async code with the other handlers.

You can execute async code from handlers of any the other events. Node will not exit for as long as there are any events on the event loop, so things like open sockets or active timeouts (created with for example setTimeout or setInterval), which usually accompany async code will keep the process alive, even if it received a signal trekking it to exit. The idea of a signal handler is to close all sockets, stop timers etc so that the process can exit by itself.

Here’s a piece of code I’ve been using (in non-effect apps) to handle signals:

export const wireSignalHandlers = (
  nodeProcess: NodeProcessForSignalHandler,
  onShutdown: AppStopFunction,
  timeoutMs: number,
): void => {
  // Wrapping with R.once since we could get both singnal in quick succesion
  // but we only want this fuction to be called once.
  const onSignal = R.once((signal: string) => {
    // eslint-disable-next-line no-param-reassign, functional/immutable-data
    nodeProcess.exitCode = 0

    // Kill the process if not exited gracefully within timeout period.
    setTimeout(
      () => {
        const error = new Error(
          `Killing process after timeout of ${timeoutMs} ms from receiving `
            + `signal: ${signal}`,
        )
        logger.error(error)
        // NOTE: do not call process.exit() anywhere else in the code base.
        nodeProcess.exit(1)
      },
      timeoutMs,
    )
      // Make sure this timeout doesn’t prevent node from exiting
      .unref()

    Promise
      .resolve(onShutdown()?.then(() => {}))
      .catch(handleUnknownError(nodeProcess, logger))
  })

  signals.forEach(signal => nodeProcess.once(signal, onSignal))
}

The above would be called from a function similar to runMain from effect.