3
3
require "action_cable/subscription_adapter/base"
4
4
require "action_cable/subscription_adapter/channel_prefix"
5
5
require "action_cable/subscription_adapter/subscriber_map"
6
+ require "concurrent/atomic/semaphore"
6
7
7
8
module ActionCable
8
9
module SubscriptionAdapter
@@ -38,34 +39,53 @@ def listener
38
39
end
39
40
40
41
class Listener < ::ActionCable ::SubscriptionAdapter ::SubscriberMap
42
+ Stop = Class . new ( Exception )
43
+
41
44
def initialize ( event_loop )
42
45
super ( )
43
46
44
47
@event_loop = event_loop
45
48
49
+ # Critical section begins with 0 permits. It can be understood as
50
+ # being "normally held" by the listener thread. It is released
51
+ # for specific sections of code, rather than acquired.
52
+ @critical = Concurrent ::Semaphore . new ( 0 )
53
+
46
54
@thread = Thread . new do
47
- Thread . current . abort_on_exception = true
48
55
listen
49
56
end
50
57
end
51
58
52
59
def listen
53
60
loop do
54
- break unless running?
55
-
56
- with_polling_volume { broadcast_messages }
61
+ begin
62
+ instance = interruptible { Rails . application . executor . run! }
63
+ with_polling_volume { broadcast_messages }
64
+ ensure
65
+ instance . complete! if instance
66
+ end
57
67
58
- interruptible_sleep ::SolidCable . polling_interval
68
+ interruptible { sleep ::SolidCable . polling_interval }
59
69
end
70
+ rescue Stop
71
+ ensure
72
+ @critical . release
60
73
end
61
74
62
- def shutdown
63
- self . running = false
64
- wake_up
75
+ def interruptible
76
+ @critical . release
77
+ yield
78
+ ensure
79
+ @critical . acquire
80
+ end
65
81
66
- ActiveSupport ::Dependencies . interlock . permit_concurrent_loads do
67
- thread &.join
68
- end
82
+ def shutdown
83
+ @critical . acquire
84
+ # We have the critical permit, and so the listen thread must be
85
+ # safe to interrupt.
86
+ thread . raise ( Stop )
87
+ @critical . release
88
+ thread . join
69
89
end
70
90
71
91
def add_channel ( channel , on_success )
@@ -83,15 +103,7 @@ def invoke_callback(*)
83
103
84
104
private
85
105
attr_reader :event_loop , :thread
86
- attr_writer :running , :last_id
87
-
88
- def running?
89
- if defined? ( @running )
90
- @running
91
- else
92
- self . running = true
93
- end
94
- end
106
+ attr_writer :last_id
95
107
96
108
def last_id
97
109
@last_id ||= ::SolidCable ::Message . maximum ( :id ) || 0
@@ -102,12 +114,10 @@ def channels
102
114
end
103
115
104
116
def broadcast_messages
105
- Rails . application . executor . wrap do
106
- ::SolidCable ::Message . broadcastable ( channels , last_id ) .
107
- each do |message |
108
- broadcast ( message . channel , message . payload )
109
- self . last_id = message . id
110
- end
117
+ ::SolidCable ::Message . broadcastable ( channels , last_id ) .
118
+ each do |message |
119
+ broadcast ( message . channel , message . payload )
120
+ self . last_id = message . id
111
121
end
112
122
end
113
123
0 commit comments