Prevent spinning when a container is stopped but not destroyed

Prevent spinning when a container is stopped but not destroyed

diff --git a/lib/mobystash/container.rb b/lib/mobystash/container.rb
index 88df413..c8e25a9 100644
--- a/lib/mobystash/container.rb
+++ b/lib/mobystash/container.rb
@@ -145,31 +145,36 @@ module Mobystash
 
     def process_events(conn)
       if @capture_logs
-        @logger.debug(progname) { "Capturing logs since #{@last_log_timestamp}" }
-
         begin
-          # The implementation of Docker::Container#streaming_logs has a
-          # *terribad* memory leak, in that every log entry that gets received
-          # gets stored in a couple of arrays, which only gets cleared when
-          # the call to #streaming_logs finishes... which is bad, because
-          # we like these to go on for a long time.  So, instead, we need to
-          # do our own thing directly, by hand.
-          chunk_parser = Mobystash::MobyChunkParser.new(tty: tty?(conn)) do |msg, s|
-            send_event(msg, s)
+          unless Docker::Container.get(@id, {}, conn).info.fetch("State", {}).fetch("Running")
+            @logger.debug(progname) { "Container is not running; waiting for it to start or be destroyed" }
+            wait_for_container_to_start(conn)
+          else
+            @logger.debug(progname) { "Capturing logs since #{@last_log_timestamp}" }
+
+            # The implementation of Docker::Container#streaming_logs has a
+            # *terribad* memory leak, in that every log entry that gets received
+            # gets stored in a couple of arrays, which only gets cleared when
+            # the call to #streaming_logs finishes... which is bad, because
+            # we like these to go on for a long time.  So, instead, we need to
+            # do our own thing directly, by hand.
+            chunk_parser = Mobystash::MobyChunkParser.new(tty: tty?(conn)) do |msg, s|
+              send_event(msg, s)
+            end
+
+            conn.get(
+              "/containers/#{@id}/logs",
+              {
+                since: (Time.strptime(@last_log_timestamp, "%FT%T.%N%Z") + ONE_NANOSECOND).strftime("%s.%N"),
+                timestamps: true,
+                follow: true,
+                stdout: true,
+                stderr: true,
+              },
+              idempotent: false,
+              response_block: chunk_parser
+            )
           end
-
-          conn.get(
-            "/containers/#{@id}/logs",
-            {
-              since: (Time.strptime(@last_log_timestamp, "%FT%T.%N%Z") + ONE_NANOSECOND).strftime("%s.%N"),
-              timestamps: true,
-              follow: true,
-              stdout: true,
-              stderr: true,
-            },
-            idempotent: false,
-            response_block: chunk_parser
-          )
         rescue Docker::Error::NotFoundError, Docker::Error::ServerError
           # This happens when the container terminates, but we beat the System
           # in the race and we call Docker::Container.get before the System
@@ -184,6 +189,18 @@ module Mobystash
       end
     end
 
+    def wait_for_container_to_start(conn)
+      @logger.debug(progname) { "Asking for events since @last_log_timestamp}" }
+
+      Docker::Event.since(@last_log_timestamp, {}, conn) do |event|
+        @last_log_timestamp = event.time
+
+        @logger.debug(progname) { "Docker event@#{event.timeNano}: #{event.Type}.#{event.Action} on #{event.ID}" }
+
+        break if event.Type == "container" && event.ID == @id
+      end
+    end
+
     def send_event(msg, stream)
       @config.log_entries_read_counter.increment(container_name: @name, container_id: @id, stream: stream)
 
diff --git a/spec/container_spec.rb b/spec/container_spec.rb
index 9231beb..488967b 100644
--- a/spec/container_spec.rb
+++ b/spec/container_spec.rb
@@ -55,7 +55,7 @@ describe Mobystash::Container do
       allow(mock_conn).to receive(:get).and_raise(Mobystash::MobyEventWorker.const_get(:TerminateEventWorker))
       allow(Docker::Container).to receive(:new).with(instance_of(Docker::Connection), instance_of(Hash)).and_call_original
       allow(Docker::Container).to receive(:get).with(container_id, {}, mock_conn).and_return(mock_moby_container)
-      allow(mock_moby_container).to receive(:info).and_return("Config" => { "Tty" => false })
+      allow(mock_moby_container).to receive(:info).and_return("Config" => { "Tty" => false }, "State" => { "Running" => true })
 
       # I'm a bit miffed we have to do this; to my mind, a double should
       # lie a little
@@ -354,7 +354,7 @@ describe Mobystash::Container do
       let(:container_id)   { "asdfasdftty" }
 
       before :each do
-        allow(mock_moby_container).to receive(:info).and_return("Config" => { "Tty" => true })
+        allow(mock_moby_container).to receive(:info).and_return("Config" => { "Tty" => true }, "State" => { "Running" => true })
       end
 
       it "asks for a pro-TTY chunk parser" do
@@ -671,7 +671,7 @@ describe Mobystash::Container do
       allow(Docker::Connection).to receive(:new).with("unix:///var/run/docker.sock", read_timeout: 3600).and_return(mock_conn)
       allow(Docker::Container).to receive(:new).with(instance_of(Docker::Connection), instance_of(Hash)).and_call_original
       allow(Docker::Container).to receive(:get).with(container_id, {}, mock_conn).and_return(mock_moby_container)
-      allow(mock_moby_container).to receive(:info).and_return("Config" => { "Tty" => false })
+      allow(mock_moby_container).to receive(:info).and_return("Config" => { "Tty" => false }, "State" => { "Running" => true })
 
       # I'm a bit miffed we have to do this; to my mind, a double should
       # lie a little

GitHub sha: 01e4ad23