From 6e3cb40935f25c717b959d42b9533cd37349187a Mon Sep 17 00:00:00 2001 From: Schneems Date: Mon, 16 Dec 2024 11:10:20 -0600 Subject: [PATCH] Make background tasks lazy 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 ' 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 ``` --- CHANGELOG.md | 2 ++ .../code_command/background/log/clear.rb | 9 ++++-- .../code_command/background/log/read.rb | 9 ++++-- lib/rundoc/code_command/background/start.rb | 32 ++++++++++++------- .../code_command/background/stdin_write.rb | 11 +++++-- lib/rundoc/code_command/background/stop.rb | 11 +++++-- lib/rundoc/code_command/background/wait.rb | 9 ++++-- test/rundoc/code_commands/background_test.rb | 16 ++++++---- 8 files changed, 69 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 736ea72..19bff4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/lib/rundoc/code_command/background/log/clear.rb b/lib/rundoc/code_command/background/log/clear.rb index 470d1ef..c583baf 100644 --- a/lib/rundoc/code_command/background/log/clear.rb +++ b/lib/rundoc/code_command/background/log/clear.rb @@ -1,7 +1,12 @@ 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 = {}) @@ -9,7 +14,7 @@ def to_md(env = {}) end def call(env = {}) - @spawn.log.truncate(0) + background.log.truncate(0) "" end end diff --git a/lib/rundoc/code_command/background/log/read.rb b/lib/rundoc/code_command/background/log/read.rb index d80dd29..eaddc05 100644 --- a/lib/rundoc/code_command/background/log/read.rb +++ b/lib/rundoc/code_command/background/log/read.rb @@ -1,7 +1,12 @@ 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 = {}) @@ -9,7 +14,7 @@ def to_md(env = {}) end def call(env = {}) - @spawn.log.read + background.log.read end end end diff --git a/lib/rundoc/code_command/background/start.rb b/lib/rundoc/code_command/background/start.rb index 4324987..679edb6 100644 --- a/lib/rundoc/code_command/background/start.rb +++ b/lib/rundoc/code_command/background/start.rb @@ -3,20 +3,28 @@ 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 = {}) @@ -24,14 +32,14 @@ def to_md(env = {}) 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 diff --git a/lib/rundoc/code_command/background/stdin_write.rb b/lib/rundoc/code_command/background/stdin_write.rb index 6164471..089af3a 100644 --- a/lib/rundoc/code_command/background/stdin_write.rb +++ b/lib/rundoc/code_command/background/stdin_write.rb @@ -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. @@ -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, diff --git a/lib/rundoc/code_command/background/stop.rb b/lib/rundoc/code_command/background/stop.rb index 9349df4..70c6535 100644 --- a/lib/rundoc/code_command/background/stop.rb +++ b/lib/rundoc/code_command/background/stop.rb @@ -1,7 +1,12 @@ 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 = {}) @@ -9,8 +14,8 @@ def to_md(env = {}) end def call(env = {}) - @spawn.stop - @spawn.log.read + background.stop + background.log.read end end end diff --git a/lib/rundoc/code_command/background/wait.rb b/lib/rundoc/code_command/background/wait.rb index 3b3de81..5de3747 100644 --- a/lib/rundoc/code_command/background/wait.rb +++ b/lib/rundoc/code_command/background/wait.rb @@ -1,9 +1,14 @@ 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 = {}) @@ -11,7 +16,7 @@ def to_md(env = {}) end def call(env = {}) - @spawn.wait(@wait, @timeout_value) + background.wait(@wait, @timeout_value) "" end end diff --git a/test/rundoc/code_commands/background_test.rb b/test/rundoc/code_commands/background_test.rb index ecb62cf..e17331c 100644 --- a/test/rundoc/code_commands/background_test.rb +++ b/test/rundoc/code_commands/background_test.rb @@ -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(