Skip to content

Commit

Permalink
Make background tasks lazy
Browse files Browse the repository at this point in the history
Previously when a background task was defined it would perform work on initialization, this fails if the first task is not actually executed yet:

```term
:::>- pre.erb background.start('heroku run:inside <%= dyno_ps_name %> "bash"', wait: "$", timeout: 120, name: "heroku_run")
:::-- background.stdin_write("ls -lah", name: "heroku_run", wait: "$", timeout: 30)
:::-- background.stdin_write("exit", name: "heroku_run", wait: "exit")
:::-> background.stop(name: "heroku_run")
```

Gives:

```
/Users/rschneeman/.gem/ruby/3.3.1/gems/rundoc-4.1.1/lib/rundoc/code_command/background/process_spawn.rb:41:in `find': Could not find task with name "heroku_run", known task names: [] (RuntimeError)
	from /Users/rschneeman/.gem/ruby/3.3.1/gems/rundoc-4.1.1/lib/rundoc/code_command/background/stdin_write.rb:9:in `initialize'
	from /Users/rschneeman/.gem/ruby/3.3.1/gems/rundoc-4.1.1/lib/rundoc.rb:13:in `new'
	from /Users/rschneeman/.gem/ruby/3.3.1/gems/rundoc-4.1.1/lib/rundoc.rb:13:in `code_command_from_keyword'
	from /Users/rschneeman/.gem/ruby/3.3.1/gems/rundoc-4.1.1/lib/rundoc/peg_parser.rb:210:in `block in <class:PegTransformer>'
	from /Users/rschneeman/.gem/ruby/3.3.1/gems/parslet-2.0.0/lib/parslet/transform.rb:217:in `instance_eval'
	from /Users/rschneeman/.gem/ruby/3.3.1/gems/parslet-2.0.0/lib/parslet/transform.rb:217:in `call_on_match'
	from /Users/rschneeman/.gem/ruby/3.3.1/gems/parslet-2.0.0/lib/parslet/transform.rb:235:in `block in transform_elt'
	from /Users/rschneeman/.gem/ruby/3.3.1/gems/parslet-2.0.0/lib/parslet/transform.rb:232:in `each'
```

Because the `background.stdin_write` call to `new()` happens at parse time, but the `background.start` call to `new()` happens at runtime, (Because `pre.erb` is the command, and it hasn't yet created the start command and pushed it onto the stack).

This change makes the lookup for background tasks lazy, so as long as the task is started by execution (runtime) of the command it will succeed.

The added test fails on main (with the error below) and passes on this branch.

```
  1) Error:
BackgroundTest#test_stdin_with_cat_echo:
RuntimeError: Could not find task with name "cat", known task names: ["script", "tail"]
    lib/rundoc/code_command/background/process_spawn.rb:41:in `find'
    lib/rundoc/code_command/background/stdin_write.rb:9:in `initialize'
    test/rundoc/code_commands/background_test.rb:9:in `new'
    test/rundoc/code_commands/background_test.rb:9:in `block (2 levels) in test_stdin_with_cat_echo'
    test/rundoc/code_commands/background_test.rb:6:in `chdir'
    test/rundoc/code_commands/background_test.rb:6:in `block in test_stdin_with_cat_echo'
    /Users/rschneeman/.rubies/ruby-3.3.1/lib/ruby/3.3.0/tmpdir.rb:99:in `mktmpdir'
    test/rundoc/code_commands/background_test.rb:5:in `test_stdin_with_cat_echo'

87 runs, 249 assertions, 0 failures, 1 errors, 0 skips
```
  • Loading branch information
schneems committed Dec 16, 2024
1 parent 29d9278 commit 6e3cb40
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 30 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## HEAD

- Fix: Background task name lookup is now lazy, this fixes a bug when using `:::>- pre.erb background.start(...)` (https://github.com/zombocom/rundoc/pull/95)

## 4.1.1

- Fix: Visibility forwarding for `pre.erb` was accidentally reversed, this is now fixed. (https://github.com/zombocom/rundoc/pull/93)
Expand Down
9 changes: 7 additions & 2 deletions lib/rundoc/code_command/background/log/clear.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
class Rundoc::CodeCommand::Background::Log
class Clear < Rundoc::CodeCommand
def initialize(name:)
@spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
@name = name
@background = nil
end

def background
@background ||= Rundoc::CodeCommand::Background::ProcessSpawn.find(@name)
end

def to_md(env = {})
""
end

def call(env = {})
@spawn.log.truncate(0)
background.log.truncate(0)
""
end
end
Expand Down
9 changes: 7 additions & 2 deletions lib/rundoc/code_command/background/log/read.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
class Rundoc::CodeCommand::Background::Log
class Read < Rundoc::CodeCommand
def initialize(name:)
@spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
@name = name
@background = nil
end

def background
@background ||= Rundoc::CodeCommand::Background::ProcessSpawn.find(@name)
end

def to_md(env = {})
""
end

def call(env = {})
@spawn.log.read
background.log.read
end
end
end
Expand Down
32 changes: 20 additions & 12 deletions lib/rundoc/code_command/background/start.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,43 @@
class Rundoc::CodeCommand::Background
class Start < Rundoc::CodeCommand
def initialize(command, name:, wait: nil, timeout: 5, log: Tempfile.new("log"), out: "2>&1", allow_fail: false)
@timeout = timeout
@command = command
@name = name
@wait = wait
@allow_fail = allow_fail
FileUtils.touch(log)
@log = log
@redirect = out
FileUtils.touch(@log)

@spawn = ProcessSpawn.new(
@background = nil
end

def background
@background ||= ProcessSpawn.new(
@command,
timeout: timeout,
log: log,
out: out
)
puts "Spawning commmand: `#{@spawn.command}`"
ProcessSpawn.add(@name, @spawn)
timeout: @timeout,
log: @log,
out: @redirect
).tap do |spawn|
puts "Spawning commmand: `#{spawn.command}`"
ProcessSpawn.add(@name, spawn)
end
end

def to_md(env = {})
"$ #{@command}"
end

def call(env = {})
@spawn.wait(@wait)
@spawn.check_alive! unless @allow_fail
background.wait(@wait)
background.check_alive! unless @allow_fail

@spawn.log.read
background.log.read
end

def alive?
!!@spawn.alive?
!!background.alive?
end
end
end
Expand Down
11 changes: 8 additions & 3 deletions lib/rundoc/code_command/background/stdin_write.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ class StdinWrite < Rundoc::CodeCommand
def initialize(contents, name:, wait:, timeout: 5, ending: $/)
@contents = contents
@ending = ending
@spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
@wait = wait
@name = name
@timeout_value = Integer(timeout)
@contents_written = nil
@background = nil
end

def background
@background ||= Rundoc::CodeCommand::Background::ProcessSpawn.find(@name)
end

# The command is rendered (`:::>-`) by the output of the `def call` method.
Expand All @@ -20,11 +25,11 @@ def to_md(env = {})
# The contents produced by the command (`:::->`) are rendered by the `def to_md` method.
def call(env = {})
writecontents
@spawn.log.read
background.log.read
end

def writecontents
@contents_written ||= @spawn.stdin_write(
@contents_written ||= background.stdin_write(
contents,
wait: @wait,
ending: @ending,
Expand Down
11 changes: 8 additions & 3 deletions lib/rundoc/code_command/background/stop.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
class Rundoc::CodeCommand::Background
class Stop < Rundoc::CodeCommand
def initialize(name:)
@spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
@name = name
@background = nil
end

def background
@background ||= Rundoc::CodeCommand::Background::ProcessSpawn.find(@name)
end

def to_md(env = {})
""
end

def call(env = {})
@spawn.stop
@spawn.log.read
background.stop
background.log.read
end
end
end
Expand Down
9 changes: 7 additions & 2 deletions lib/rundoc/code_command/background/wait.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
class Rundoc::CodeCommand::Background
class Wait < Rundoc::CodeCommand
def initialize(name:, wait:, timeout: 5)
@spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
@name = name
@wait = wait
@timeout_value = Integer(timeout)
@background = nil
end

def background
@background ||= Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
end

def to_md(env = {})
""
end

def call(env = {})
@spawn.wait(@wait, @timeout_value)
background.wait(@wait, @timeout_value)
""
end
end
Expand Down
16 changes: 10 additions & 6 deletions test/rundoc/code_commands/background_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ class BackgroundTest < Minitest::Test
def test_stdin_with_cat_echo
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
background_start = Rundoc::CodeCommand::Background::Start.new("cat",
name: "cat")
background_start.call

output = Rundoc::CodeCommand::Background::StdinWrite.new(
# Intentionally out of order, should not raise an error as long as "cat"
# command exists at execution time
stdin_write = Rundoc::CodeCommand::Background::StdinWrite.new(
"hello there",
name: "cat",
wait: "hello"
).call
)

background_start = Rundoc::CodeCommand::Background::Start.new("cat",
name: "cat")

background_start.call
output = stdin_write.call
assert_equal("hello there" + $/, output)

Rundoc::CodeCommand::Background::Log::Clear.new(
Expand Down

0 comments on commit 6e3cb40

Please sign in to comment.