diff --git a/examples/async.rb b/examples/async.rb deleted file mode 100644 index fde882a..0000000 --- a/examples/async.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2020-2022, by Samuel Williams. - -require 'kernel/sync' - -class Worker - def initialize(&block) - - end -end - -Sync do - count.times do - worker = Worker.new(&block) - - status = worker.wait do |message| - - end - - status.success? - status.failed? - end -end diff --git a/examples/grace/server.rb b/examples/grace/server.rb new file mode 100755 index 0000000..2efd5d0 --- /dev/null +++ b/examples/grace/server.rb @@ -0,0 +1,72 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2024, by Samuel Williams. + +require '../../lib/async/container' +require 'async/io/host_endpoint' + +Console.logger.debug! + +module SignalWrapper + def self.trap(signal, &block) + signal = signal + + original = Signal.trap(signal) do + ::Signal.trap(signal, original) + block.call + end + end +end + +class Controller < Async::Container::Controller + def initialize(...) + super + + @endpoint = Async::IO::Endpoint.tcp("localhost", 8080) + @bound_endpoint = nil + end + + def start + Console.debug(self) {"Binding to #{@endpoint}"} + @bound_endpoint = Sync{@endpoint.bound} + + super + end + + def setup(container) + container.run count: 2, restart: true do |instance| + SignalWrapper.trap(:INT) do + Console.debug(self) {"Closing bound instance..."} + @bound_endpoint.close + end + + Sync do |task| + Console.info(self) {"Starting bound instance..."} + + instance.ready! + + @bound_endpoint.accept do |peer| + while true + peer.write("#{Time.now.to_s.rjust(32)}: Hello World\n") + sleep 1 + end + end + end + end + end + + def stop(graceful = true) + super + + if @bound_endpoint + @bound_endpoint.close + @bound_endpoint = nil + end + end +end + +controller = Controller.new + +controller.run diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index e68ff20..1bdc115 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -22,7 +22,7 @@ class Controller # Initialize the controller. # @parameter notify [Notify::Client] A client used for process readiness notifications. - def initialize(notify: Notify.open!, container_class: Container) + def initialize(notify: Notify.open!, container_class: Container, graceful: true) @container = nil @container_class = container_class @@ -35,6 +35,8 @@ def initialize(notify: Notify.open!, container_class: Container) trap(SIGHUP) do self.restart end + + @graceful = graceful end # The state of the controller. @@ -96,7 +98,7 @@ def start # Stop the container if it's running. # @parameter graceful [Boolean] Whether to give the children instances time to shut down or to kill them immediately. - def stop(graceful = true) + def stop(graceful = @graceful) @container&.stop(graceful) @container = nil end @@ -130,7 +132,7 @@ def restart if container.failed? @notify&.error!("Container failed to start!") - container.stop + container.stop(false) raise SetupError, container end @@ -142,7 +144,7 @@ def restart if old_container Console.logger.debug(self, "Stopping old container...") - old_container&.stop + old_container&.stop(@graceful) end @notify&.ready! @@ -211,11 +213,11 @@ def run end end rescue Interrupt - self.stop(true) + self.stop rescue Terminate self.stop(false) ensure - self.stop(true) + self.stop(false) # Restore the interrupt handler: Signal.trap(:INT, interrupt_action) diff --git a/lib/async/container/group.rb b/lib/async/container/group.rb index 2f56fb7..52e2e08 100644 --- a/lib/async/container/group.rb +++ b/lib/async/container/group.rb @@ -133,6 +133,7 @@ def wait_for(channel) protected def wait_for_children(duration = nil) + Console.debug(self, "Waiting for children...", duration: duration) if !@running.empty? # Maybe consider using a proper event loop here: readable, _, _ = ::IO.select(@running.keys, nil, nil, duration)