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

Allow multiple concurrent one-off dynos to run #94

Merged
merged 3 commits into from
Aug 4, 2020

Conversation

schneems
Copy link
Contributor

@schneems schneems commented Jul 28, 2020

By default free apps can only run one "one-off" dyno at a time. For example if you run heroku run rails c you cannot run heroku run ls until the first command finishes (on a free app). There's a quirk about the distributed nature of the heroku platform in that once heroku run <command> returns, it does not guarantee that the dyno has completely been shutdown, but only that shutdown has started. This means that if you're wanting to do multiple app.run calls in a test, you need to add sleeps to be safe:

Hatchet::Runner.new("default_ruby").deploy do |app|
  expect(app.run("ls -a Gemfile 'foo bar #baz'")).to match(/ls: cannot access 'foo bar #baz': No such file or directory\s+Gemfile/)
  sleep 4
  expect((0 != $?.exitstatus)).to be_truthy
  app.run("ls erpderp", heroku: ({ "exit-code" => (Hatchet::App::SkipDefaultOption) }))
  sleep 4
  expect((0 == $?.exitstatus)).to be_truthy
  app.run("ls erpderp", heroku: ({ "no-tty" => nil }))
end

Which is not great.

Enable multiple one-off dynos

This PR allows an individual application to flag itself into being able to run multiple concurrent dynos by setting the run_multi: true config at initialization time. However, to use this setting you must set the env var HEROKU_EXPENSIVE_MODE=1 as a safety gate to ensure you understand the risks.

Now the above tests will look like this (without the sleep):

Hatchet::Runner.new("default_ruby", run_multi: true).deploy do |app|
  expect(app.run("ls -a Gemfile 'foo bar #baz'")).to match(/ls: cannot access 'foo bar #baz': No such file or directory\s+Gemfile/)
  expect((0 != $?.exitstatus)).to be_truthy

  app.run("ls erpderp", heroku: ({ "exit-code" => (Hatchet::App::SkipDefaultOption) }))
  expect((0 == $?.exitstatus)).to be_truthy

  app.run("ls erpderp", heroku: ({ "no-tty" => nil }))
end

NEAT!

Run multi feature

The REALLY cool feature this opens up is that you no longer have to wait for each of your heroku run commands to return. They can now be executed concurrently using the new run_multi command:

Hatchet::Runner.new("default_ruby", run_multi: true).deploy do |app|
  app.run_multi("ls") do |out, status|
    expect(status.success?).to be_truthy
    expect(out).to include("Gemfile")
  end
  app.run_multi("ruby -v") do |out, status|
    expect(status.success?).to be_truthy
    expect(out).to include("ruby")
  end
end

In this example the heroku run ls and heroku run ruby -v will be executed concurrently. The order that the run_multi blocks execute is not guaranteed.

You can toggle this run_multi setting on globally by using HATCHET_RUN_MULTI=1. Without this setting enabled, you might need to add a sleep between multiple app.run invocations. WARNING: Enabling this run_multi setting will charge your application account. To work, this requires your application to have a web process associated with it.

@schneems schneems force-pushed the schneems/concurrent-run branch 2 times, most recently from 676ca75 to fa86afe Compare July 28, 2020 14:21
@schneems schneems force-pushed the schneems/concurrent-run branch 2 times, most recently from 037d90e to e37e8ff Compare August 3, 2020 20:06
By default free apps can only run one "one-off" dyno at a time. For example if you run `heroku run rails c` you cannot run `heroku run ls` until the first command finishes (on a free app). There's a quirk about the distributed nature of the heroku platform in that once `heroku run <command>` returns, it does not guarantee that the dyno has completely been shutdown, but only that shutdown has started. This means that if you're wanting to do multiple `app.run` calls in a test, you need to add sleeps to be safe:

```
Hatchet::Runner.new("default_ruby").deploy do |app|
  expect(app.run("ls -a Gemfile 'foo bar #baz'")).to match(/ls: cannot access 'foo bar #baz': No such file or directory\s+Gemfile/)
  sleep 4
  expect((0 != $?.exitstatus)).to be_truthy
  app.run("ls erpderp", heroku: ({ "exit-code" => (Hatchet::App::SkipDefaultOption) }))
  sleep 4
  expect((0 == $?.exitstatus)).to be_truthy
  app.run("ls erpderp", heroku: ({ "no-tty" => nil }))
end```

Which is not great.

## Enable multiple one-off dynos

This PR allows an individual application to flag itself into being able to run multiple concurrent dynos by setting the `run_multi: true` config at initialization time. However, to use this setting you must set the env var `HEROKU_EXPENSIVE_MODE=1` as a safety gate to ensure you understand the risks.

Now the above tests will look like this (without the sleep):

```ruby
Hatchet::Runner.new("default_ruby", run_multi: true).deploy do |app|
  expect(app.run("ls -a Gemfile 'foo bar #baz'")).to match(/ls: cannot access 'foo bar #baz': No such file or directory\s+Gemfile/)
  expect((0 != $?.exitstatus)).to be_truthy

  app.run("ls erpderp", heroku: ({ "exit-code" => (Hatchet::App::SkipDefaultOption) }))
  expect((0 == $?.exitstatus)).to be_truthy

  app.run("ls erpderp", heroku: ({ "no-tty" => nil }))
end
```

NEAT!

## Run multi feature

The REALLY cool feature this opens up is that you no longer have to wait for each of your `heroku run` commands to return. They can now be executed concurrently using the new `run_multi` command:

```ruby
Hatchet::Runner.new("default_ruby", run_multi: true).deploy do |app|
  app.run_multi("ls") do |out, status|
    expect(status.success?).to be_truthy
    expect(out).to include("Gemfile")
  end
  app.run_multi("ruby -v") do |out, status|
    expect(status.success?).to be_truthy
    expect(out).to include("ruby")
  end
end
```

In this example the `heroku run ls` and `heroku run ruby -v` will be executed concurrently. The order that the `run_multi` blocks execute is not guaranteed.

You can toggle this `run_multi` setting on globally by using `HATCHET_RUN_MULTI=1`. Without this setting enabled, you might need to add a `sleep` between multiple `app.run` invocations. WARNING: Enabling this `run_multi` setting will charge your application account. To work, this requires your application to have a `web` process associated with it.
@schneems
Copy link
Contributor Author

schneems commented Aug 4, 2020

Related issue: grosser/parallel_split_test#7 (comment)

@schneems schneems merged commit 5d51852 into main Aug 4, 2020
@schneems schneems deleted the schneems/concurrent-run branch August 4, 2020 19:36
schneems added a commit to heroku/heroku-buildpack-python that referenced this pull request Aug 11, 2020
- ActiveSupport's Object#blank? and Object#present? are no longer provided by default (heroku/hatchet#107)
- Remove deprecated support for passing a block to `App#run` (heroku/hatchet#105)
- Ignore  403 on app delete due to race condition (heroku/hatchet#101)
- The hatchet.lock file can now be locked to "main" in addition to "master" (heroku/hatchet#86)
- Allow concurrent one-off dyno runs with the `run_multi: true` flag on apps (heroku/hatchet#94)
- Apps are now marked as being "finished" by enabling maintenance mode on them when `teardown!` is called. Finished apps can be reaped immediately (heroku/hatchet#97)
- Applications that are not marked as "finished" will be allowed to live for a HATCHET_ALIVE_TTL_MINUTES duration before they're deleted by the reaper to protect against deleting an app mid-deploy, default is seven minutes (heroku/hatchet#97)
- The HEROKU_APP_LIMIT env var no longer does anything, instead hatchet application reaping is manually executed if an app cannot be created (heroku/hatchet#97)
- App#deploy without a block will no longer run `teardown!` automatically (heroku/hatchet#97)
- Calls to `git push heroku` are now rate throttled (heroku/hatchet#98)
- Calls to `app.run` are now rate throttled (heroku/hatchet#99)
- Deployment now raises and error when the release failed (heroku/hatchet#93)
schneems added a commit to heroku/heroku-buildpack-php that referenced this pull request Aug 11, 2020
- ActiveSupport's Object#blank? and Object#present? are no longer provided by default (heroku/hatchet#107)
- Remove deprecated support for passing a block to `App#run` (heroku/hatchet#105)
- Ignore  403 on app delete due to race condition (heroku/hatchet#101)
- The hatchet.lock file can now be locked to "main" in addition to "master" (heroku/hatchet#86)
- Allow concurrent one-off dyno runs with the `run_multi: true` flag on apps (heroku/hatchet#94)
- Apps are now marked as being "finished" by enabling maintenance mode on them when `teardown!` is called. Finished apps can be reaped immediately (heroku/hatchet#97)
- Applications that are not marked as "finished" will be allowed to live for a HATCHET_ALIVE_TTL_MINUTES duration before they're deleted by the reaper to protect against deleting an app mid-deploy, default is seven minutes (heroku/hatchet#97)
- The HEROKU_APP_LIMIT env var no longer does anything, instead hatchet application reaping is manually executed if an app cannot be created (heroku/hatchet#97)
- App#deploy without a block will no longer run `teardown!` automatically (heroku/hatchet#97)
- Calls to `git push heroku` are now rate throttled (heroku/hatchet#98)
- Calls to `app.run` are now rate throttled (heroku/hatchet#99)
- Deployment now raises and error when the release failed (heroku/hatchet#93)
edmorley pushed a commit to heroku/heroku-buildpack-python that referenced this pull request Aug 11, 2020
- ActiveSupport's Object#blank? and Object#present? are no longer provided by default (heroku/hatchet#107)
- Remove deprecated support for passing a block to `App#run` (heroku/hatchet#105)
- Ignore  403 on app delete due to race condition (heroku/hatchet#101)
- The hatchet.lock file can now be locked to "main" in addition to "master" (heroku/hatchet#86)
- Allow concurrent one-off dyno runs with the `run_multi: true` flag on apps (heroku/hatchet#94)
- Apps are now marked as being "finished" by enabling maintenance mode on them when `teardown!` is called. Finished apps can be reaped immediately (heroku/hatchet#97)
- Applications that are not marked as "finished" will be allowed to live for a HATCHET_ALIVE_TTL_MINUTES duration before they're deleted by the reaper to protect against deleting an app mid-deploy, default is seven minutes (heroku/hatchet#97)
- The HEROKU_APP_LIMIT env var no longer does anything, instead hatchet application reaping is manually executed if an app cannot be created (heroku/hatchet#97)
- App#deploy without a block will no longer run `teardown!` automatically (heroku/hatchet#97)
- Calls to `git push heroku` are now rate throttled (heroku/hatchet#98)
- Calls to `app.run` are now rate throttled (heroku/hatchet#99)
- Deployment now raises and error when the release failed (heroku/hatchet#93)

[skip changelog]
schneems added a commit to heroku/heroku-buildpack-jvm-common that referenced this pull request Aug 11, 2020
- ActiveSupport's Object#blank? and Object#present? are no longer provided by default (heroku/hatchet#107)
- Remove deprecated support for passing a block to `App#run` (heroku/hatchet#105)
- Ignore  403 on app delete due to race condition (heroku/hatchet#101)
- The hatchet.lock file can now be locked to "main" in addition to "master" (heroku/hatchet#86)
- Allow concurrent one-off dyno runs with the `run_multi: true` flag on apps (heroku/hatchet#94)
- Apps are now marked as being "finished" by enabling maintenance mode on them when `teardown!` is called. Finished apps can be reaped immediately (heroku/hatchet#97)
- Applications that are not marked as "finished" will be allowed to live for a HATCHET_ALIVE_TTL_MINUTES duration before they're deleted by the reaper to protect against deleting an app mid-deploy, default is seven minutes (heroku/hatchet#97)
- The HEROKU_APP_LIMIT env var no longer does anything, instead hatchet application reaping is manually executed if an app cannot be created (heroku/hatchet#97)
- App#deploy without a block will no longer run `teardown!` automatically (heroku/hatchet#97)
- Calls to `git push heroku` are now rate throttled (heroku/hatchet#98)
- Calls to `app.run` are now rate throttled (heroku/hatchet#99)
- Deployment now raises and error when the release failed (heroku/hatchet#93)
schneems added a commit to heroku/heroku-buildpack-scala that referenced this pull request Aug 11, 2020
- ActiveSupport's Object#blank? and Object#present? are no longer provided by default (heroku/hatchet#107)
- Remove deprecated support for passing a block to `App#run` (heroku/hatchet#105)
- Ignore  403 on app delete due to race condition (heroku/hatchet#101)
- The hatchet.lock file can now be locked to "main" in addition to "master" (heroku/hatchet#86)
- Allow concurrent one-off dyno runs with the `run_multi: true` flag on apps (heroku/hatchet#94)
- Apps are now marked as being "finished" by enabling maintenance mode on them when `teardown!` is called. Finished apps can be reaped immediately (heroku/hatchet#97)
- Applications that are not marked as "finished" will be allowed to live for a HATCHET_ALIVE_TTL_MINUTES duration before they're deleted by the reaper to protect against deleting an app mid-deploy, default is seven minutes (heroku/hatchet#97)
- The HEROKU_APP_LIMIT env var no longer does anything, instead hatchet application reaping is manually executed if an app cannot be created (heroku/hatchet#97)
- App#deploy without a block will no longer run `teardown!` automatically (heroku/hatchet#97)
- Calls to `git push heroku` are now rate throttled (heroku/hatchet#98)
- Calls to `app.run` are now rate throttled (heroku/hatchet#99)
- Deployment now raises and error when the release failed (heroku/hatchet#93)
schneems added a commit to heroku/heroku-buildpack-clojure that referenced this pull request Aug 11, 2020
- ActiveSupport's Object#blank? and Object#present? are no longer provided by default (heroku/hatchet#107)
- Remove deprecated support for passing a block to `App#run` (heroku/hatchet#105)
- Ignore  403 on app delete due to race condition (heroku/hatchet#101)
- The hatchet.lock file can now be locked to "main" in addition to "master" (heroku/hatchet#86)
- Allow concurrent one-off dyno runs with the `run_multi: true` flag on apps (heroku/hatchet#94)
- Apps are now marked as being "finished" by enabling maintenance mode on them when `teardown!` is called. Finished apps can be reaped immediately (heroku/hatchet#97)
- Applications that are not marked as "finished" will be allowed to live for a HATCHET_ALIVE_TTL_MINUTES duration before they're deleted by the reaper to protect against deleting an app mid-deploy, default is seven minutes (heroku/hatchet#97)
- The HEROKU_APP_LIMIT env var no longer does anything, instead hatchet application reaping is manually executed if an app cannot be created (heroku/hatchet#97)
- App#deploy without a block will no longer run `teardown!` automatically (heroku/hatchet#97)
- Calls to `git push heroku` are now rate throttled (heroku/hatchet#98)
- Calls to `app.run` are now rate throttled (heroku/hatchet#99)
- Deployment now raises and error when the release failed (heroku/hatchet#93)
schneems added a commit to heroku/heroku-buildpack-java that referenced this pull request Aug 11, 2020
- ActiveSupport's Object#blank? and Object#present? are no longer provided by default (heroku/hatchet#107)
- Remove deprecated support for passing a block to `App#run` (heroku/hatchet#105)
- Ignore  403 on app delete due to race condition (heroku/hatchet#101)
- The hatchet.lock file can now be locked to "main" in addition to "master" (heroku/hatchet#86)
- Allow concurrent one-off dyno runs with the `run_multi: true` flag on apps (heroku/hatchet#94)
- Apps are now marked as being "finished" by enabling maintenance mode on them when `teardown!` is called. Finished apps can be reaped immediately (heroku/hatchet#97)
- Applications that are not marked as "finished" will be allowed to live for a HATCHET_ALIVE_TTL_MINUTES duration before they're deleted by the reaper to protect against deleting an app mid-deploy, default is seven minutes (heroku/hatchet#97)
- The HEROKU_APP_LIMIT env var no longer does anything, instead hatchet application reaping is manually executed if an app cannot be created (heroku/hatchet#97)
- App#deploy without a block will no longer run `teardown!` automatically (heroku/hatchet#97)
- Calls to `git push heroku` are now rate throttled (heroku/hatchet#98)
- Calls to `app.run` are now rate throttled (heroku/hatchet#99)
- Deployment now raises and error when the release failed (heroku/hatchet#93)
schneems added a commit to heroku/heroku-buildpack-nodejs that referenced this pull request Aug 11, 2020
- ActiveSupport's Object#blank? and Object#present? are no longer provided by default (heroku/hatchet#107)
- Remove deprecated support for passing a block to `App#run` (heroku/hatchet#105)
- Ignore  403 on app delete due to race condition (heroku/hatchet#101)
- The hatchet.lock file can now be locked to "main" in addition to "master" (heroku/hatchet#86)
- Allow concurrent one-off dyno runs with the `run_multi: true` flag on apps (heroku/hatchet#94)
- Apps are now marked as being "finished" by enabling maintenance mode on them when `teardown!` is called. Finished apps can be reaped immediately (heroku/hatchet#97)
- Applications that are not marked as "finished" will be allowed to live for a HATCHET_ALIVE_TTL_MINUTES duration before they're deleted by the reaper to protect against deleting an app mid-deploy, default is seven minutes (heroku/hatchet#97)
- The HEROKU_APP_LIMIT env var no longer does anything, instead hatchet application reaping is manually executed if an app cannot be created (heroku/hatchet#97)
- App#deploy without a block will no longer run `teardown!` automatically (heroku/hatchet#97)
- Calls to `git push heroku` are now rate throttled (heroku/hatchet#98)
- Calls to `app.run` are now rate throttled (heroku/hatchet#99)
- Deployment now raises and error when the release failed (heroku/hatchet#93)
dryan pushed a commit to dryan/heroku-buildpack-python that referenced this pull request Nov 19, 2020
- ActiveSupport's Object#blank? and Object#present? are no longer provided by default (heroku/hatchet#107)
- Remove deprecated support for passing a block to `App#run` (heroku/hatchet#105)
- Ignore  403 on app delete due to race condition (heroku/hatchet#101)
- The hatchet.lock file can now be locked to "main" in addition to "master" (heroku/hatchet#86)
- Allow concurrent one-off dyno runs with the `run_multi: true` flag on apps (heroku/hatchet#94)
- Apps are now marked as being "finished" by enabling maintenance mode on them when `teardown!` is called. Finished apps can be reaped immediately (heroku/hatchet#97)
- Applications that are not marked as "finished" will be allowed to live for a HATCHET_ALIVE_TTL_MINUTES duration before they're deleted by the reaper to protect against deleting an app mid-deploy, default is seven minutes (heroku/hatchet#97)
- The HEROKU_APP_LIMIT env var no longer does anything, instead hatchet application reaping is manually executed if an app cannot be created (heroku/hatchet#97)
- App#deploy without a block will no longer run `teardown!` automatically (heroku/hatchet#97)
- Calls to `git push heroku` are now rate throttled (heroku/hatchet#98)
- Calls to `app.run` are now rate throttled (heroku/hatchet#99)
- Deployment now raises and error when the release failed (heroku/hatchet#93)

[skip changelog]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants