dherault / serverless-offline

Emulate AWS λ and API Gateway locally when developing your Serverless project
MIT License
5.19k stars 796 forks source link

Provide a way to invalidate spawned worker threads #1660

Open noppa opened 1 year ago

noppa commented 1 year ago

Feature Request

When you make code changes while developing handlers, it's pretty important that the server reliably uses the changed code when a request comes in. After v9, we are supposed to use --reloadHandler for this, so that serverless-offline will spawn new worker thread for each request that comes in, instead of reusing the existing ones. That works great.

However, the issue with --reloadHandler is that it'll reload all of the code for every request, as opposed to reloading once after each code change, making the server very slow when your client does lots of requests.

Would it be possible for serverless-offline to provide some kind of hook/API to invalidate or kill the worker threads that serverless-offline has spawned and force it to reload them when the next request comes in? So instead of thowing away the worker threads and reloading after every request, it'd instead keep the old ones around, essentially working like it does today if you omit --reloadHandler. But then there would be separate way to explicitly force it to throw existing workers away.

That way serverless-offline wouldn't even have to worry about file change monitoring etc, that concern could be moved to some plugin or other kind of wrapper. We could use the cached workers for all requests until the invalidation happens (which in practice would be after a code change), and after that everything would reload as expected.

Of course you can already get the same effect by using nodemon etc to restart the entire process, but that can be very, very slow, so it's not really a better option that just using the --reloadHandler.

noppa commented 1 year ago

Digging through the code a bit, turns out there already is such a hook, 'offline:functionsUpdated:cleanup' :thinking:

noppa commented 1 year ago

This can now be achieved with a separate plugin, serverless-offline-watcher.

plugins:
  - serverless-offline
  - serverless-offline-watcher

custom:
  serverless-offline-watcher:
    - path:
        - src/**/*
      hook: offline:functionsUpdated
ronkot commented 1 year ago

Thanks @noppa 🙏

Managed to restore "old" hot reload behavior with your approach.

Our project uses TS & webpack, so our conf looks like:

custom:
  serverless-offline-watcher:
    - path:
        - .webpack/**/*
      hook: offline:functionsUpdated
nevolgograd commented 11 months ago

@noppa @ronkot

I tried your configuration, but unfortunately, it didn't work for me. I'm also using TS and serverless-webpack. Could you please provide more details about your setup? Thanks in advance!

ronkot commented 11 months ago

@nevolgograd What kind of problem do you have? Our hot reload setup is not perfect either - there're occasional crashes due to some missing compiled js file (or something). Restarting serverless-offline server fixes these problems, so I've not worried about that.

nevolgograd commented 11 months ago

@ronkot hot reload just doesn't work, I mean no difference before and after serverless-offline-watcher. I run command like this, but regular serverless offline start doesn't work either.

"local": "cross-env NODE_ENV=development node --inspect node_modules/serverless/bin/serverless offline start --useInProcess --verbose"

My webpack output:

output: {
    library: {
      type: 'commonjs2',
    },
    path: '../.webpack',
    filename: '[name].js',
  },

And serverless part:

plugins:
  - serverless-webpack
  - serverless-offline
  - serverless-offline-sns
  - serverless-offline-watcher

package:
  individually: true

custom:
  webpack:
    webpackConfig: ./webpack/webpack.config.${env:NODE_ENV, 'production'}.js
    packager: 'yarn'
    includeModules:
      packagePath: './packages/app/package.json'
      nodeModulesRelativeDir: './'
    packagerOptions:
      noFrozenLockfile: true

  serverless-offline-watcher:
    - path:
        - .webpack/service/src/lambda/http.js
      hook: offline:functionsUpdated

  serverless-offline:
    host: 0.0.0.0
    httpPort: 8080
    websocketPort: 8100
    lambdaPort: 8200
    noPrependStageInUrl: true
    noAuth: true
    printOutput: true
    allowCache: true
ronkot commented 11 months ago

Okay, looks good to me 🤔 I'm not sure of these, but you could try running serverless-offline without the --useInProcess flag. Also, try to debug print the serverless-offline-watcher events as shown in https://github.com/domdomegg/serverless-offline-watcher README. I'd use a file selector wildcard as a starting point and when it works, narrow the selector as needed.

nevolgograd commented 11 months ago

@ronkot thanks for the tip. Indeed, without --useInProcess flag whole handler was reloaded after the request. It's pretty much the same as --reloadHandler which is not the best experience.

I expected this to be ideal solution for debugging and hot reloading, but guess it's not, unfortunately.

I'm sorry, I was mistaken. This method works, and the handler is only refreshed once after each modification which is effective. I wish it would only work with "in-process" so that I could use the node inspector