Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions fixtures/async/container/a_container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,27 @@ module Container
)
end
end

it "can exec with ready: true without premature termination" do
container.spawn(restart: false) do |instance|
# Using exec with ready: true should not cause the process to be killed
# by hang prevention, even though the notification pipe stays open.
instance.exec("sleep", "1", ready: true)
end

# Wait for the process to become ready:
container.wait_until_ready

# Sleep longer than the hang prevention timeout (0.1s) to verify
# the process isn't prematurely killed:
sleep(0.2)

# The process should still be running (not killed by hang prevention):
expect(container).to be(:running?)

# Now stop the container:
container.stop(false)
end
end

with "#sleep" do
Expand Down
8 changes: 5 additions & 3 deletions lib/async/container/forked.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,15 @@ def name
# This method replaces the child process with the new executable, thus this method never returns.
#
# @parameter arguments [Array] The arguments to pass to the new process.
# @parameter ready [Boolean] If true, informs the parent process that the child is ready. Otherwise, the child process will need to use a notification protocol to inform the parent process that it is ready.
# @parameter ready [Boolean] If true, informs the parent process that the child is ready before exec. The notification pipe will still be passed to the exec'd process to prevent premature termination.
# @parameter options [Hash] Additional options to pass to {::Process.exec}.
def exec(*arguments, ready: true, **options)
# Always set up the notification pipe to be inherited by the exec'd process.
# This prevents the pipe from closing, which would trigger hang prevention and SIGKILL.
self.before_spawn(arguments, options)

if ready
self.ready!(status: "(exec)")
else
self.before_spawn(arguments, options)
end

::Process.exec(*arguments, **options)
Expand Down
5 changes: 3 additions & 2 deletions lib/async/container/threaded.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,11 @@ def name
# Execute a child process using {::Process.spawn}. In order to simulate {::Process.exec}, an {Exit} instance is raised to propagage exit status.
# This creates the illusion that this method does not return (normally).
def exec(*arguments, ready: true, **options)
# Always set up the notification pipe to be inherited by the spawned process.
self.before_spawn(arguments, options)

if ready
self.ready!(status: "(spawn)")
else
self.before_spawn(arguments, options)
end

begin
Expand Down
4 changes: 4 additions & 0 deletions releases.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Releases

## Unreleased

- **Fixed**: `instance.exec` with `ready: true` no longer causes premature termination. The notification pipe is now always passed to the exec'd process.

## v0.34.4

- Add missing `bake` and `context` files to the release.
Expand Down
Loading