aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
authorMatthew Draper <matthew@trebex.net>2014-06-01 10:35:00 +0930
committerMatthew Draper <matthew@trebex.net>2014-06-08 07:21:14 +0930
commit6a89850dfe1e8c8331fd8482525aa4b9b2530cad (patch)
tree1f46c8533080b07a1f0ab9cef99ad45fc3be961c /actionpack/lib/action_controller
parentdc73e39b4d40f0965b000f84568f77f126ec8290 (diff)
downloadrails-6a89850dfe1e8c8331fd8482525aa4b9b2530cad.tar.gz
rails-6a89850dfe1e8c8331fd8482525aa4b9b2530cad.tar.bz2
rails-6a89850dfe1e8c8331fd8482525aa4b9b2530cad.zip
Handle client disconnect during live streaming
.. even when the producer is blocked for a write.
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/metal/live.rb48
1 files changed, 48 insertions, 0 deletions
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 4c0554d27b..67875141cb 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -107,12 +107,25 @@ module ActionController
end
end
+ class ClientDisconnected < RuntimeError
+ end
+
class Buffer < ActionDispatch::Response::Buffer #:nodoc:
include MonitorMixin
+ # Ignore that the client has disconnected.
+ #
+ # If this value is `true`, calling `write` after the client
+ # disconnects will result in the written content being silently
+ # discarded. If this value is `false` (the default), a
+ # ClientDisconnected exception will be raised.
+ attr_accessor :ignore_disconnect
+
def initialize(response)
@error_callback = lambda { true }
@cv = new_cond
+ @aborted = false
+ @ignore_disconnect = false
super(response, SizedQueue.new(10))
end
@@ -123,6 +136,17 @@ module ActionController
end
super
+
+ unless connected?
+ @buf.clear
+
+ unless @ignore_disconnect
+ # Raise ClientDisconnected, which is a RuntimeError (not an
+ # IOError), because that's more appropriate for something beyond
+ # the developer's control.
+ raise ClientDisconnected, "client disconnected"
+ end
+ end
end
def each
@@ -133,6 +157,10 @@ module ActionController
@response.sent!
end
+ # Write a 'close' event to the buffer; the producer/writing thread
+ # uses this to notify us that it's finished supplying content.
+ #
+ # See also #abort.
def close
synchronize do
super
@@ -141,6 +169,26 @@ module ActionController
end
end
+ # Inform the producer/writing thread that the client has
+ # disconnected; the reading thread is no longer interested in
+ # anything that's being written.
+ #
+ # See also #close.
+ def abort
+ synchronize do
+ @aborted = true
+ @buf.clear
+ end
+ end
+
+ # Is the client still connected and waiting for content?
+ #
+ # The result of calling `write` when this is `false` is determined
+ # by `ignore_disconnect`.
+ def connected?
+ !@aborted
+ end
+
def await_close
synchronize do
@cv.wait_until { @closed }